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
@@ -0,0 +1,504 @@
1
+ import React from 'react'
2
+ import { Tabs, Tab, Stack } from './tab'
3
+
4
+ // Pruebas unitarias para los componentes Tab mejorados
5
+ describe('Enhanced Tab Components', () => {
6
+ // Mock de los componentes dependientes
7
+ const mockIcon = jest.fn()
8
+ const mockText = jest.fn()
9
+
10
+ beforeEach(() => {
11
+ jest.clearAllMocks()
12
+
13
+ // Mock de componentes
14
+ jest.doMock('./icon', () => ({ Icon: mockIcon }))
15
+ jest.doMock('./text', () => ({ Text: mockText }))
16
+
17
+ // Mock de console.warn
18
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
19
+
20
+ // Mock de localStorage
21
+ const localStorageMock = {
22
+ getItem: jest.fn(),
23
+ setItem: jest.fn(),
24
+ removeItem: jest.fn(),
25
+ clear: jest.fn(),
26
+ }
27
+ global.localStorage = localStorageMock
28
+ })
29
+
30
+ afterEach(() => {
31
+ console.warn.mockRestore()
32
+ })
33
+
34
+ // Tabs Component Tests
35
+ describe('Tabs Component', () => {
36
+ test('component exports correctly', () => {
37
+ expect(Tabs).toBeDefined()
38
+ expect(typeof Tabs).toBe('function')
39
+ })
40
+
41
+ test('component has correct PropTypes', () => {
42
+ expect(Tabs.propTypes).toBeDefined()
43
+ expect(Tabs.propTypes.children).toBeDefined()
44
+ expect(Tabs.propTypes.selected).toBeDefined()
45
+ expect(Tabs.propTypes.onChange).toBeDefined()
46
+ expect(Tabs.propTypes.fillLeft).toBeDefined()
47
+ expect(Tabs.propTypes.fillRight).toBeDefined()
48
+ expect(Tabs.propTypes.orientation).toBeDefined()
49
+ expect(Tabs.propTypes.variant).toBeDefined()
50
+ expect(Tabs.propTypes.disabled).toBeDefined()
51
+ expect(Tabs.propTypes.animated).toBeDefined()
52
+ expect(Tabs.propTypes.persistent).toBeDefined()
53
+ })
54
+
55
+ test('component has correct defaultProps', () => {
56
+ expect(Tabs.defaultProps).toBeDefined()
57
+ expect(Tabs.defaultProps.fillLeft).toBe(false)
58
+ expect(Tabs.defaultProps.fillRight).toBe(true)
59
+ expect(Tabs.defaultProps.orientation).toBe('horizontal')
60
+ expect(Tabs.defaultProps.variant).toBe('standard')
61
+ expect(Tabs.defaultProps.disabled).toBe(false)
62
+ expect(Tabs.defaultProps.animated).toBe(true)
63
+ expect(Tabs.defaultProps.persistent).toBe(false)
64
+ })
65
+
66
+ test('warns when children is empty', () => {
67
+ const validateChildren = (children) => {
68
+ if (children && !React.Children.count(children)) {
69
+ console.warn('Tabs component: children prop should contain Tab components')
70
+ }
71
+ }
72
+
73
+ validateChildren([])
74
+ expect(console.warn).toHaveBeenCalledWith('Tabs component: children prop should contain Tab components')
75
+
76
+ console.warn.mockClear()
77
+ validateChildren(null)
78
+ expect(console.warn).not.toHaveBeenCalled()
79
+ })
80
+
81
+ test('handles selection correctly', () => {
82
+ const mockOnChange = jest.fn()
83
+
84
+ const handleSelect = async (id, index, disabled, beforeChange, onChange, selected) => {
85
+ if (disabled) return
86
+
87
+ if (beforeChange) {
88
+ try {
89
+ const canChange = await beforeChange(id || index, selected)
90
+ if (canChange === false) return
91
+ } catch (error) {
92
+ console.warn('Tabs beforeChange hook error:', error)
93
+ return
94
+ }
95
+ }
96
+
97
+ if (onChange) onChange(id || index)
98
+ }
99
+
100
+ // Normal selection
101
+ handleSelect('tab1', 0, false, null, mockOnChange, null)
102
+ expect(mockOnChange).toHaveBeenCalledWith('tab1')
103
+
104
+ mockOnChange.mockClear()
105
+
106
+ // Disabled state
107
+ handleSelect('tab1', 0, true, null, mockOnChange, null)
108
+ expect(mockOnChange).not.toHaveBeenCalled()
109
+ })
110
+
111
+ test('handles beforeChange hook correctly', async () => {
112
+ const mockOnChange = jest.fn()
113
+ const mockBeforeChange = jest.fn()
114
+
115
+ const handleSelect = async (id, index, disabled, beforeChange, onChange, selected) => {
116
+ if (disabled) return
117
+
118
+ if (beforeChange) {
119
+ try {
120
+ const canChange = await beforeChange(id || index, selected)
121
+ if (canChange === false) return
122
+ } catch (error) {
123
+ console.warn('Tabs beforeChange hook error:', error)
124
+ return
125
+ }
126
+ }
127
+
128
+ if (onChange) onChange(id || index)
129
+ }
130
+
131
+ // Allow change
132
+ mockBeforeChange.mockResolvedValue(true)
133
+ await handleSelect('tab1', 0, false, mockBeforeChange, mockOnChange, 'tab0')
134
+ expect(mockBeforeChange).toHaveBeenCalledWith('tab1', 'tab0')
135
+ expect(mockOnChange).toHaveBeenCalledWith('tab1')
136
+
137
+ mockOnChange.mockClear()
138
+ mockBeforeChange.mockClear()
139
+
140
+ // Prevent change
141
+ mockBeforeChange.mockResolvedValue(false)
142
+ await handleSelect('tab1', 0, false, mockBeforeChange, mockOnChange, 'tab0')
143
+ expect(mockBeforeChange).toHaveBeenCalledWith('tab1', 'tab0')
144
+ expect(mockOnChange).not.toHaveBeenCalled()
145
+
146
+ mockOnChange.mockClear()
147
+ mockBeforeChange.mockClear()
148
+
149
+ // Error in beforeChange
150
+ mockBeforeChange.mockRejectedValue(new Error('Test error'))
151
+ await handleSelect('tab1', 0, false, mockBeforeChange, mockOnChange, 'tab0')
152
+ expect(console.warn).toHaveBeenCalledWith('Tabs beforeChange hook error:', expect.any(Error))
153
+ expect(mockOnChange).not.toHaveBeenCalled()
154
+ })
155
+
156
+ test('handles keyboard navigation correctly', () => {
157
+ const mockHandleSelect = jest.fn()
158
+ const children = [
159
+ { props: { id: 'tab1' } },
160
+ { props: { id: 'tab2' } },
161
+ { props: { id: 'tab3' } }
162
+ ]
163
+
164
+ const handleKeyDown = (event, disabled, selected, notNullChildren, handleSelect) => {
165
+ if (disabled) return
166
+
167
+ const currentIndex = typeof selected === 'number' ? selected :
168
+ notNullChildren.findIndex(child => child.props.id === selected)
169
+
170
+ let newIndex = currentIndex
171
+
172
+ switch (event.key) {
173
+ case 'ArrowLeft':
174
+ case 'ArrowUp':
175
+ event.preventDefault()
176
+ newIndex = currentIndex > 0 ? currentIndex - 1 : notNullChildren.length - 1
177
+ break
178
+ case 'ArrowRight':
179
+ case 'ArrowDown':
180
+ event.preventDefault()
181
+ newIndex = currentIndex < notNullChildren.length - 1 ? currentIndex + 1 : 0
182
+ break
183
+ case 'Home':
184
+ event.preventDefault()
185
+ newIndex = 0
186
+ break
187
+ case 'End':
188
+ event.preventDefault()
189
+ newIndex = notNullChildren.length - 1
190
+ break
191
+ default:
192
+ return
193
+ }
194
+
195
+ const targetChild = notNullChildren[newIndex]
196
+ if (targetChild && !targetChild.props.disabled) {
197
+ handleSelect(targetChild.props.id, newIndex)
198
+ }
199
+ }
200
+
201
+ const mockEvent = { preventDefault: jest.fn() }
202
+
203
+ // Arrow right from first tab
204
+ handleKeyDown({ ...mockEvent, key: 'ArrowRight' }, false, 0, children, mockHandleSelect)
205
+ expect(mockHandleSelect).toHaveBeenCalledWith('tab2', 1)
206
+
207
+ mockHandleSelect.mockClear()
208
+
209
+ // Arrow left from last tab (wrap around)
210
+ handleKeyDown({ ...mockEvent, key: 'ArrowLeft' }, false, 2, children, mockHandleSelect)
211
+ expect(mockHandleSelect).toHaveBeenCalledWith('tab2', 1)
212
+
213
+ mockHandleSelect.mockClear()
214
+
215
+ // Home key
216
+ handleKeyDown({ ...mockEvent, key: 'Home' }, false, 2, children, mockHandleSelect)
217
+ expect(mockHandleSelect).toHaveBeenCalledWith('tab1', 0)
218
+
219
+ mockHandleSelect.mockClear()
220
+
221
+ // End key
222
+ handleKeyDown({ ...mockEvent, key: 'End' }, false, 0, children, mockHandleSelect)
223
+ expect(mockHandleSelect).toHaveBeenCalledWith('tab3', 2)
224
+ })
225
+
226
+ test('generates CSS classes correctly', () => {
227
+ const generateClasses = (orientation, variant, scrollable, centered, disabled, animated, className) => {
228
+ return [
229
+ 'tabs',
230
+ `tabs--${orientation}`,
231
+ `tabs--${variant}`,
232
+ scrollable && 'tabs--scrollable',
233
+ centered && 'tabs--centered',
234
+ disabled && 'tabs--disabled',
235
+ animated && 'tabs--animated',
236
+ className
237
+ ].filter(Boolean).join(' ')
238
+ }
239
+
240
+ expect(generateClasses('horizontal', 'standard', false, false, false, false, ''))
241
+ .toBe('tabs tabs--horizontal tabs--standard')
242
+
243
+ expect(generateClasses('vertical', 'scrollable', true, true, true, true, 'custom'))
244
+ .toBe('tabs tabs--vertical tabs--scrollable tabs--scrollable tabs--centered tabs--disabled tabs--animated custom')
245
+ })
246
+
247
+ test('generates accessibility attributes correctly', () => {
248
+ const generateAriaAttributes = (ariaLabel, disabled, orientation) => {
249
+ return {
250
+ 'aria-label': ariaLabel || 'Tabs',
251
+ 'aria-disabled': disabled,
252
+ 'aria-orientation': orientation,
253
+ role: 'tablist'
254
+ }
255
+ }
256
+
257
+ const result1 = generateAriaAttributes(null, false, 'horizontal')
258
+ expect(result1['aria-label']).toBe('Tabs')
259
+ expect(result1['aria-disabled']).toBe(false)
260
+ expect(result1['aria-orientation']).toBe('horizontal')
261
+ expect(result1.role).toBe('tablist')
262
+
263
+ const result2 = generateAriaAttributes('Custom Tabs', true, 'vertical')
264
+ expect(result2['aria-label']).toBe('Custom Tabs')
265
+ expect(result2['aria-disabled']).toBe(true)
266
+ expect(result2['aria-orientation']).toBe('vertical')
267
+ })
268
+
269
+ test('handles persistent tabs correctly', () => {
270
+ const mockOnChange = jest.fn()
271
+
272
+ // Test persistence logic
273
+ const testPersistence = (persistent, persistKey, savedValue, currentSelected) => {
274
+ if (persistent && persistKey && savedValue !== null) {
275
+ const parsedTab = isNaN(savedValue) ? savedValue : parseInt(savedValue)
276
+ if (parsedTab !== currentSelected) {
277
+ return parsedTab
278
+ }
279
+ }
280
+ return currentSelected
281
+ }
282
+
283
+ // Test loading saved tab
284
+ const result1 = testPersistence(true, 'test-tabs', '2', 0)
285
+ expect(result1).toBe(2)
286
+
287
+ // Test no change when same tab
288
+ const result2 = testPersistence(true, 'test-tabs', '1', 1)
289
+ expect(result2).toBe(1)
290
+
291
+ // Test disabled persistence
292
+ const result3 = testPersistence(false, 'test-tabs', '2', 0)
293
+ expect(result3).toBe(0)
294
+
295
+ // Test save logic
296
+ const testSave = (persistent, persistKey, selected) => {
297
+ if (persistent && persistKey && selected !== undefined) {
298
+ return `tabs-${persistKey}:${selected.toString()}`
299
+ }
300
+ return null
301
+ }
302
+
303
+ expect(testSave(true, 'test-tabs', 1)).toBe('tabs-test-tabs:1')
304
+ expect(testSave(false, 'test-tabs', 1)).toBe(null)
305
+ })
306
+ })
307
+
308
+ // Tab Component Tests
309
+ describe('Tab Component', () => {
310
+ test('component has correct PropTypes', () => {
311
+ expect(Tab.propTypes).toBeDefined()
312
+ expect(Tab.propTypes.id).toBeDefined()
313
+ expect(Tab.propTypes.icon).toBeDefined()
314
+ expect(Tab.propTypes.label).toBeDefined()
315
+ expect(Tab.propTypes.selected).toBeDefined()
316
+ expect(Tab.propTypes.disabled).toBeDefined()
317
+ expect(Tab.propTypes.closeable).toBeDefined()
318
+ expect(Tab.propTypes.badge).toBeDefined()
319
+ })
320
+
321
+ test('component has correct defaultProps', () => {
322
+ expect(Tab.defaultProps).toBeDefined()
323
+ expect(Tab.defaultProps.disabled).toBe(false)
324
+ expect(Tab.defaultProps.closeable).toBe(false)
325
+ expect(Tab.defaultProps.animated).toBe(true)
326
+ })
327
+
328
+ test('handles selection correctly', () => {
329
+ const mockOnSelect = jest.fn()
330
+
331
+ const handleSelect = (event, disabled, onSelect, id) => {
332
+ if (disabled) return
333
+ event.preventDefault()
334
+ if (onSelect) onSelect(id)
335
+ }
336
+
337
+ const mockEvent = { preventDefault: jest.fn() }
338
+
339
+ // Normal selection
340
+ handleSelect(mockEvent, false, mockOnSelect, 'tab1')
341
+ expect(mockEvent.preventDefault).toHaveBeenCalled()
342
+ expect(mockOnSelect).toHaveBeenCalledWith('tab1')
343
+
344
+ mockOnSelect.mockClear()
345
+
346
+ // Disabled state
347
+ handleSelect(mockEvent, true, mockOnSelect, 'tab1')
348
+ expect(mockOnSelect).not.toHaveBeenCalled()
349
+ })
350
+
351
+ test('handles close correctly', () => {
352
+ const mockOnClose = jest.fn()
353
+
354
+ const handleClose = (event, onClose, id) => {
355
+ event.stopPropagation()
356
+ if (onClose) onClose(id)
357
+ }
358
+
359
+ const mockEvent = { stopPropagation: jest.fn() }
360
+
361
+ handleClose(mockEvent, mockOnClose, 'tab1')
362
+ expect(mockEvent.stopPropagation).toHaveBeenCalled()
363
+ expect(mockOnClose).toHaveBeenCalledWith('tab1')
364
+ })
365
+
366
+ test('handles keyboard interaction correctly', () => {
367
+ const mockOnSelect = jest.fn()
368
+ const mockOnClose = jest.fn()
369
+
370
+ const handleKeyDown = (event, disabled, onSelect, id, closeable, onClose) => {
371
+ if (disabled) return
372
+
373
+ switch (event.key) {
374
+ case 'Enter':
375
+ case ' ':
376
+ event.preventDefault()
377
+ if (onSelect) onSelect(id)
378
+ break
379
+ case 'Delete':
380
+ case 'Backspace':
381
+ if (closeable && onClose) {
382
+ event.preventDefault()
383
+ onClose(id)
384
+ }
385
+ break
386
+ default:
387
+ break
388
+ }
389
+ }
390
+
391
+ const mockEvent = { preventDefault: jest.fn() }
392
+
393
+ // Enter key
394
+ handleKeyDown({ ...mockEvent, key: 'Enter' }, false, mockOnSelect, 'tab1', false, null)
395
+ expect(mockOnSelect).toHaveBeenCalledWith('tab1')
396
+
397
+ mockOnSelect.mockClear()
398
+
399
+ // Delete key with closeable tab
400
+ handleKeyDown({ ...mockEvent, key: 'Delete' }, false, mockOnSelect, 'tab1', true, mockOnClose)
401
+ expect(mockOnClose).toHaveBeenCalledWith('tab1')
402
+ })
403
+
404
+ test('generates CSS classes correctly', () => {
405
+ const generateClasses = (selected, disabled, closeable, animated, orientation, variant, className) => {
406
+ return [
407
+ 'tab',
408
+ selected && 'selected',
409
+ disabled && 'tab--disabled',
410
+ closeable && 'tab--closeable',
411
+ animated && 'tab--animated',
412
+ `tab--${orientation}`,
413
+ `tab--${variant}`,
414
+ className
415
+ ].filter(Boolean).join(' ')
416
+ }
417
+
418
+ expect(generateClasses(false, false, false, false, 'horizontal', 'standard', ''))
419
+ .toBe('tab tab--horizontal tab--standard')
420
+
421
+ expect(generateClasses(true, true, true, true, 'vertical', 'scrollable', 'custom'))
422
+ .toBe('tab selected tab--disabled tab--closeable tab--animated tab--vertical tab--scrollable custom')
423
+ })
424
+
425
+ test('generates accessibility attributes correctly', () => {
426
+ const generateAriaAttributes = (selected, disabled, id, tabIndex, label) => {
427
+ return {
428
+ 'aria-selected': selected,
429
+ 'aria-disabled': disabled,
430
+ 'aria-controls': `tabpanel-${id || tabIndex}`,
431
+ 'aria-label': typeof label === 'string' ? label : `Tab ${(tabIndex || 0) + 1}`,
432
+ role: 'tab',
433
+ tabIndex: selected ? 0 : -1,
434
+ id: `tab-${id || tabIndex}`
435
+ }
436
+ }
437
+
438
+ const result1 = generateAriaAttributes(true, false, 'tab1', 0, 'First Tab')
439
+ expect(result1['aria-selected']).toBe(true)
440
+ expect(result1['aria-disabled']).toBe(false)
441
+ expect(result1['aria-controls']).toBe('tabpanel-tab1')
442
+ expect(result1['aria-label']).toBe('First Tab')
443
+ expect(result1.role).toBe('tab')
444
+ expect(result1.tabIndex).toBe(0)
445
+ expect(result1.id).toBe('tab-tab1')
446
+
447
+ const result2 = generateAriaAttributes(false, true, null, 1, null)
448
+ expect(result2['aria-selected']).toBe(false)
449
+ expect(result2['aria-disabled']).toBe(true)
450
+ expect(result2['aria-label']).toBe('Tab 2')
451
+ expect(result2.tabIndex).toBe(-1)
452
+ })
453
+ })
454
+
455
+ // Stack Component Tests
456
+ describe('Stack Component', () => {
457
+ test('component has correct PropTypes', () => {
458
+ expect(Stack.propTypes).toBeDefined()
459
+ expect(Stack.propTypes.selected).toBeDefined()
460
+ expect(Stack.propTypes.children).toBeDefined()
461
+ expect(Stack.propTypes.lazy).toBeDefined()
462
+ expect(Stack.propTypes.keepMounted).toBeDefined()
463
+ expect(Stack.propTypes.animated).toBeDefined()
464
+ })
465
+
466
+ test('component has correct defaultProps', () => {
467
+ expect(Stack.defaultProps).toBeDefined()
468
+ expect(Stack.defaultProps.selected).toBe(0)
469
+ expect(Stack.defaultProps.lazy).toBe(false)
470
+ expect(Stack.defaultProps.keepMounted).toBe(false)
471
+ expect(Stack.defaultProps.animated).toBe(true)
472
+ })
473
+
474
+ test('handles lazy loading correctly', () => {
475
+ const handleLazyLoading = (selected, lazy, keepMounted, mountedTabs) => {
476
+ if (lazy || keepMounted) {
477
+ return new Set([...mountedTabs, selected])
478
+ }
479
+ return mountedTabs
480
+ }
481
+
482
+ const initialMounted = new Set([0])
483
+
484
+ // Add new tab to mounted set
485
+ const newMounted = handleLazyLoading(2, true, false, initialMounted)
486
+ expect(newMounted.has(0)).toBe(true)
487
+ expect(newMounted.has(2)).toBe(true)
488
+ expect(newMounted.has(1)).toBe(false)
489
+ })
490
+
491
+ test('generates CSS classes correctly', () => {
492
+ const generateClasses = (animated, className) => {
493
+ return [
494
+ 'stack',
495
+ animated && 'stack--animated',
496
+ className
497
+ ].filter(Boolean).join(' ')
498
+ }
499
+
500
+ expect(generateClasses(false, '')).toBe('stack')
501
+ expect(generateClasses(true, 'custom')).toBe('stack stack--animated custom')
502
+ })
503
+ })
504
+ })