@moontra/moonui 2.0.7 → 2.0.9
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/package.json +26 -19
- package/src/__tests__/use-intersection-observer.test.tsx +216 -0
- package/src/__tests__/use-local-storage.test.tsx +174 -0
- package/src/__tests__/use-pro-access.test.tsx +183 -0
- package/src/components/ui/__tests__/accordion.test.tsx +571 -0
- package/src/components/ui/__tests__/alert.test.tsx +484 -0
- package/src/components/ui/__tests__/aspect-ratio.test.tsx +492 -0
- package/src/components/ui/__tests__/avatar.test.tsx +382 -0
- package/src/components/ui/__tests__/badge.test.tsx +364 -0
- package/src/components/ui/__tests__/breadcrumb.test.tsx +687 -0
- package/src/components/ui/__tests__/button.test.tsx +91 -0
- package/src/components/ui/__tests__/card.test.tsx +104 -0
- package/src/components/ui/__tests__/checkbox.test.tsx +591 -0
- package/src/components/ui/__tests__/collapsible.test.tsx +592 -0
- package/src/components/ui/__tests__/color-picker.test.tsx +365 -0
- package/src/components/ui/__tests__/command.test.tsx +677 -0
- package/src/components/ui/__tests__/data-table.test.tsx +256 -0
- package/src/components/ui/__tests__/date-picker.test.tsx +320 -0
- package/src/components/ui/__tests__/dialog.test.tsx +764 -0
- package/src/components/ui/__tests__/input.test.tsx +318 -0
- package/src/components/ui/__tests__/label.test.tsx +103 -0
- package/src/components/ui/__tests__/popover.test.tsx +637 -0
- package/src/components/ui/__tests__/progress.test.tsx +382 -0
- package/src/components/ui/__tests__/radio-group.test.tsx +555 -0
- package/src/components/ui/__tests__/select.test.tsx +705 -0
- package/src/components/ui/__tests__/skeleton.test.tsx +253 -0
- package/src/components/ui/__tests__/slider.test.tsx +493 -0
- package/src/components/ui/__tests__/switch.test.tsx +372 -0
- package/src/components/ui/__tests__/table.test.tsx +824 -0
- package/src/components/ui/__tests__/tabs.test.tsx +887 -0
- package/src/components/ui/__tests__/toast.test.tsx +458 -0
- package/src/components/ui/__tests__/tooltip.test.tsx +583 -0
- package/src/components/ui/accordion.tsx +63 -0
- package/src/components/ui/alert.tsx +130 -0
- package/src/components/ui/aspect-ratio.tsx +72 -0
- package/src/components/ui/avatar.tsx +135 -0
- package/src/components/ui/badge.tsx +225 -0
- package/src/components/ui/breadcrumb.tsx +207 -0
- package/src/components/ui/button.stories.tsx +162 -0
- package/src/components/ui/button.tsx +221 -0
- package/src/components/ui/calendar.tsx +262 -0
- package/src/components/ui/card.stories.tsx +208 -0
- package/src/components/ui/card.tsx +141 -0
- package/src/components/ui/checkbox.tsx +256 -0
- package/src/components/ui/collapsible.tsx +129 -0
- package/src/components/ui/color-picker.tsx +664 -0
- package/src/components/ui/command.tsx +218 -0
- package/src/components/ui/data-table.stories.tsx +509 -0
- package/src/components/ui/data-table.tsx +623 -0
- package/src/components/ui/date-picker.stories.tsx +321 -0
- package/src/components/ui/date-picker.tsx +603 -0
- package/src/components/ui/dialog.tsx +332 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/file-upload.tsx +400 -0
- package/src/components/ui/github-stars.tsx +201 -0
- package/src/components/ui/index.ts +12 -0
- package/src/components/ui/input.stories.tsx +255 -0
- package/src/components/ui/input.tsx +219 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/locked-component.tsx +215 -0
- package/src/components/ui/moon-logo.tsx +97 -0
- package/src/components/ui/pagination.tsx +116 -0
- package/src/components/ui/popover.tsx +183 -0
- package/src/components/ui/progress.tsx +182 -0
- package/src/components/ui/radio-group.tsx +243 -0
- package/src/components/ui/select.tsx +273 -0
- package/src/components/ui/separator.tsx +140 -0
- package/src/components/ui/simple-editor.tsx +652 -0
- package/src/components/ui/skeleton.tsx +351 -0
- package/src/components/ui/slider.tsx +351 -0
- package/src/components/ui/switch.tsx +83 -0
- package/src/components/ui/table.tsx +322 -0
- package/src/components/ui/tabs.tsx +195 -0
- package/src/components/ui/textarea.tsx +46 -0
- package/src/components/ui/toast.tsx +313 -0
- package/src/components/ui/toggle.tsx +47 -0
- package/src/components/ui/tooltip.tsx +152 -0
- package/src/docs/theme-system.md +1194 -0
- package/src/index.tsx +39 -0
- package/src/lib/micro-interactions.ts +255 -0
- package/src/lib/theme.tsx +398 -0
- package/src/lib/utils.ts +69 -0
- package/src/styles/design-system.css +365 -0
- package/src/styles/index.css +4 -0
- package/src/styles/tailwind.css +6 -0
- package/src/styles/tokens.css +453 -0
- package/src/use-intersection-observer.tsx +154 -0
- package/src/use-local-storage.tsx +71 -0
- package/src/use-paddle.ts +138 -0
- package/src/use-performance-optimizer.ts +379 -0
- package/src/use-pro-access.ts +141 -0
- package/src/use-scroll-animation.ts +221 -0
- package/src/use-subscription.ts +37 -0
- package/src/use-toast.ts +32 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import '@testing-library/jest-dom'
|
|
4
|
+
import {
|
|
5
|
+
RadioGroup,
|
|
6
|
+
RadioGroupItem,
|
|
7
|
+
RadioLabel,
|
|
8
|
+
RadioItemWithLabel,
|
|
9
|
+
} from '../radio-group'
|
|
10
|
+
|
|
11
|
+
describe('RadioGroup Components', () => {
|
|
12
|
+
describe('RadioGroup Component', () => {
|
|
13
|
+
it('renders correctly with default props', () => {
|
|
14
|
+
render(
|
|
15
|
+
<RadioGroup data-testid="radio-group">
|
|
16
|
+
<RadioGroupItem value="option1" />
|
|
17
|
+
<RadioGroupItem value="option2" />
|
|
18
|
+
</RadioGroup>
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const radioGroup = screen.getByTestId('radio-group')
|
|
22
|
+
expect(radioGroup).toBeInTheDocument()
|
|
23
|
+
expect(radioGroup).toHaveAttribute('role', 'radiogroup')
|
|
24
|
+
expect(radioGroup).toHaveClass('grid', 'gap-2')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('applies custom className', () => {
|
|
28
|
+
render(
|
|
29
|
+
<RadioGroup className="custom-class" data-testid="radio-group">
|
|
30
|
+
<RadioGroupItem value="option1" />
|
|
31
|
+
</RadioGroup>
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const radioGroup = screen.getByTestId('radio-group')
|
|
35
|
+
expect(radioGroup).toHaveClass('custom-class')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('forwards ref correctly', () => {
|
|
39
|
+
const ref = React.createRef<HTMLDivElement>()
|
|
40
|
+
render(
|
|
41
|
+
<RadioGroup ref={ref} data-testid="radio-group">
|
|
42
|
+
<RadioGroupItem value="option1" />
|
|
43
|
+
</RadioGroup>
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('handles controlled state', () => {
|
|
50
|
+
const onValueChange = jest.fn()
|
|
51
|
+
render(
|
|
52
|
+
<RadioGroup value="option2" onValueChange={onValueChange} data-testid="radio-group">
|
|
53
|
+
<RadioGroupItem value="option1" />
|
|
54
|
+
<RadioGroupItem value="option2" />
|
|
55
|
+
</RadioGroup>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const option1 = screen.getByDisplayValue('option1')
|
|
59
|
+
const option2 = screen.getByDisplayValue('option2')
|
|
60
|
+
|
|
61
|
+
expect(option1).not.toBeChecked()
|
|
62
|
+
expect(option2).toBeChecked()
|
|
63
|
+
|
|
64
|
+
fireEvent.click(option1)
|
|
65
|
+
expect(onValueChange).toHaveBeenCalledWith('option1')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('handles disabled state', () => {
|
|
69
|
+
render(
|
|
70
|
+
<RadioGroup disabled data-testid="radio-group">
|
|
71
|
+
<RadioGroupItem value="option1" />
|
|
72
|
+
<RadioGroupItem value="option2" />
|
|
73
|
+
</RadioGroup>
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const option1 = screen.getByDisplayValue('option1')
|
|
77
|
+
const option2 = screen.getByDisplayValue('option2')
|
|
78
|
+
|
|
79
|
+
expect(option1).toBeDisabled()
|
|
80
|
+
expect(option2).toBeDisabled()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('passes through HTML attributes', () => {
|
|
84
|
+
render(
|
|
85
|
+
<RadioGroup data-testid="radio-group" id="radio-group-1">
|
|
86
|
+
<RadioGroupItem value="option1" />
|
|
87
|
+
</RadioGroup>
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const radioGroup = screen.getByTestId('radio-group')
|
|
91
|
+
expect(radioGroup).toHaveAttribute('id', 'radio-group-1')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('maintains displayName', () => {
|
|
95
|
+
expect(RadioGroup.displayName).toBe('RadioGroup')
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('RadioGroupItem Component', () => {
|
|
100
|
+
it('renders correctly with default props', () => {
|
|
101
|
+
render(
|
|
102
|
+
<RadioGroup>
|
|
103
|
+
<RadioGroupItem value="test" data-testid="radio-item" />
|
|
104
|
+
</RadioGroup>
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
108
|
+
expect(radioItem).toBeInTheDocument()
|
|
109
|
+
expect(radioItem).toHaveAttribute('type', 'radio')
|
|
110
|
+
expect(radioItem).toHaveAttribute('value', 'test')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('applies custom className to label', () => {
|
|
114
|
+
render(
|
|
115
|
+
<RadioGroup>
|
|
116
|
+
<RadioGroupItem value="test" className="custom-radio" />
|
|
117
|
+
</RadioGroup>
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
121
|
+
expect(label).toHaveClass('custom-radio')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('forwards ref correctly', () => {
|
|
125
|
+
const ref = React.createRef<HTMLInputElement>()
|
|
126
|
+
render(
|
|
127
|
+
<RadioGroup>
|
|
128
|
+
<RadioGroupItem ref={ref} value="test" />
|
|
129
|
+
</RadioGroup>
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
expect(ref.current).toBeInstanceOf(HTMLInputElement)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('maintains displayName', () => {
|
|
136
|
+
expect(RadioGroupItem.displayName).toBe('RadioGroupItem')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('renders default variant correctly', () => {
|
|
140
|
+
render(
|
|
141
|
+
<RadioGroup>
|
|
142
|
+
<RadioGroupItem value="test" />
|
|
143
|
+
</RadioGroup>
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
147
|
+
expect(label).toHaveClass('border-border', 'bg-background', 'text-primary')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('renders outline variant correctly', () => {
|
|
151
|
+
render(
|
|
152
|
+
<RadioGroup>
|
|
153
|
+
<RadioGroupItem value="test" variant="outline" />
|
|
154
|
+
</RadioGroup>
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
158
|
+
expect(label).toHaveClass('border-border', 'bg-transparent', 'text-primary')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('renders filled variant correctly', () => {
|
|
162
|
+
render(
|
|
163
|
+
<RadioGroup>
|
|
164
|
+
<RadioGroupItem value="test" variant="filled" />
|
|
165
|
+
</RadioGroup>
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
169
|
+
expect(label).toHaveClass('border-primary', 'bg-primary/10', 'text-primary')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('renders sm size correctly', () => {
|
|
173
|
+
render(
|
|
174
|
+
<RadioGroup>
|
|
175
|
+
<RadioGroupItem value="test" size="sm" />
|
|
176
|
+
</RadioGroup>
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
180
|
+
expect(label).toHaveClass('h-3.5', 'w-3.5')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('renders default size correctly', () => {
|
|
184
|
+
render(
|
|
185
|
+
<RadioGroup>
|
|
186
|
+
<RadioGroupItem value="test" />
|
|
187
|
+
</RadioGroup>
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
191
|
+
expect(label).toHaveClass('h-4', 'w-4')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('renders md size correctly', () => {
|
|
195
|
+
render(
|
|
196
|
+
<RadioGroup>
|
|
197
|
+
<RadioGroupItem value="test" size="md" />
|
|
198
|
+
</RadioGroup>
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
202
|
+
expect(label).toHaveClass('h-5', 'w-5')
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('renders lg size correctly', () => {
|
|
206
|
+
render(
|
|
207
|
+
<RadioGroup>
|
|
208
|
+
<RadioGroupItem value="test" size="lg" />
|
|
209
|
+
</RadioGroup>
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
213
|
+
expect(label).toHaveClass('h-6', 'w-6')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('renders custom indicator when checked', () => {
|
|
217
|
+
const CustomIndicator = () => <span data-testid="custom-indicator">✓</span>
|
|
218
|
+
render(
|
|
219
|
+
<RadioGroup value="test">
|
|
220
|
+
<RadioGroupItem value="test" indicator={<CustomIndicator />} />
|
|
221
|
+
</RadioGroup>
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
expect(screen.getByTestId('custom-indicator')).toBeInTheDocument()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('renders default Circle indicator when checked', () => {
|
|
228
|
+
render(
|
|
229
|
+
<RadioGroup value="test">
|
|
230
|
+
<RadioGroupItem value="test" />
|
|
231
|
+
</RadioGroup>
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
const label = screen.getByDisplayValue('test').nextElementSibling
|
|
235
|
+
const indicator = label?.querySelector('svg')
|
|
236
|
+
expect(indicator).toBeInTheDocument()
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('handles disabled state', () => {
|
|
240
|
+
render(
|
|
241
|
+
<RadioGroup>
|
|
242
|
+
<RadioGroupItem value="test" disabled />
|
|
243
|
+
</RadioGroup>
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
247
|
+
expect(radioItem).toBeDisabled()
|
|
248
|
+
|
|
249
|
+
const label = radioItem.nextElementSibling
|
|
250
|
+
expect(label).toHaveClass('cursor-not-allowed', 'opacity-50')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('handles click events', () => {
|
|
254
|
+
const onValueChange = jest.fn()
|
|
255
|
+
render(
|
|
256
|
+
<RadioGroup onValueChange={onValueChange}>
|
|
257
|
+
<RadioGroupItem value="test" />
|
|
258
|
+
</RadioGroup>
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
262
|
+
fireEvent.click(radioItem)
|
|
263
|
+
expect(onValueChange).toHaveBeenCalledWith('test')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('handles onChange callback integration', () => {
|
|
267
|
+
const onValueChange = jest.fn()
|
|
268
|
+
render(
|
|
269
|
+
<RadioGroup onValueChange={onValueChange}>
|
|
270
|
+
<RadioGroupItem value="test" />
|
|
271
|
+
</RadioGroup>
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
275
|
+
|
|
276
|
+
// Click the radio item to trigger change
|
|
277
|
+
fireEvent.click(radioItem)
|
|
278
|
+
|
|
279
|
+
// onValueChange should be called with the value
|
|
280
|
+
expect(onValueChange).toHaveBeenCalledWith('test')
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('passes through HTML attributes', () => {
|
|
284
|
+
render(
|
|
285
|
+
<RadioGroup>
|
|
286
|
+
<RadioGroupItem value="test" id="radio-1" data-custom="value" />
|
|
287
|
+
</RadioGroup>
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
291
|
+
expect(radioItem).toHaveAttribute('id', 'radio-1')
|
|
292
|
+
expect(radioItem).toHaveAttribute('data-custom', 'value')
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('RadioLabel Component', () => {
|
|
297
|
+
it('renders correctly with default props', () => {
|
|
298
|
+
render(<RadioLabel htmlFor="radio-1">Test Label</RadioLabel>)
|
|
299
|
+
|
|
300
|
+
const label = screen.getByText('Test Label')
|
|
301
|
+
expect(label).toBeInTheDocument()
|
|
302
|
+
expect(label).toHaveAttribute('for', 'radio-1')
|
|
303
|
+
expect(label).toHaveClass('text-sm', 'font-medium', 'leading-none', 'ml-2', 'text-foreground')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('applies custom className', () => {
|
|
307
|
+
render(<RadioLabel className="custom-label">Test Label</RadioLabel>)
|
|
308
|
+
|
|
309
|
+
const label = screen.getByText('Test Label')
|
|
310
|
+
expect(label).toHaveClass('custom-label')
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('forwards ref correctly', () => {
|
|
314
|
+
const ref = React.createRef<HTMLLabelElement>()
|
|
315
|
+
render(<RadioLabel ref={ref}>Test Label</RadioLabel>)
|
|
316
|
+
|
|
317
|
+
expect(ref.current).toBeInstanceOf(HTMLLabelElement)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('handles disabled state', () => {
|
|
321
|
+
render(<RadioLabel disabled>Test Label</RadioLabel>)
|
|
322
|
+
|
|
323
|
+
const label = screen.getByText('Test Label')
|
|
324
|
+
expect(label).toHaveClass('cursor-not-allowed', 'opacity-70')
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('passes through HTML attributes', () => {
|
|
328
|
+
render(<RadioLabel id="label-1" data-custom="value">Test Label</RadioLabel>)
|
|
329
|
+
|
|
330
|
+
const label = screen.getByText('Test Label')
|
|
331
|
+
expect(label).toHaveAttribute('id', 'label-1')
|
|
332
|
+
expect(label).toHaveAttribute('data-custom', 'value')
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('maintains displayName', () => {
|
|
336
|
+
expect(RadioLabel.displayName).toBe('RadioLabel')
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
describe('RadioItemWithLabel Component', () => {
|
|
341
|
+
it('renders correctly with default props', () => {
|
|
342
|
+
render(
|
|
343
|
+
<RadioGroup>
|
|
344
|
+
<RadioItemWithLabel value="test" label="Test Option" />
|
|
345
|
+
</RadioGroup>
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
349
|
+
const label = screen.getByText('Test Option')
|
|
350
|
+
|
|
351
|
+
expect(radioItem).toBeInTheDocument()
|
|
352
|
+
expect(label).toBeInTheDocument()
|
|
353
|
+
expect(label).toHaveAttribute('for', radioItem.id)
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('applies custom labelClassName', () => {
|
|
357
|
+
render(
|
|
358
|
+
<RadioGroup>
|
|
359
|
+
<RadioItemWithLabel value="test" label="Test Option" labelClassName="custom-label" />
|
|
360
|
+
</RadioGroup>
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
const label = screen.getByText('Test Option')
|
|
364
|
+
expect(label).toHaveClass('custom-label')
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('forwards ref correctly', () => {
|
|
368
|
+
const ref = React.createRef<HTMLInputElement>()
|
|
369
|
+
render(
|
|
370
|
+
<RadioGroup>
|
|
371
|
+
<RadioItemWithLabel ref={ref} value="test" label="Test Option" />
|
|
372
|
+
</RadioGroup>
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
expect(ref.current).toBeInstanceOf(HTMLInputElement)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('handles disabled state', () => {
|
|
379
|
+
render(
|
|
380
|
+
<RadioGroup>
|
|
381
|
+
<RadioItemWithLabel value="test" label="Test Option" disabled />
|
|
382
|
+
</RadioGroup>
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
386
|
+
const label = screen.getByText('Test Option')
|
|
387
|
+
|
|
388
|
+
expect(radioItem).toBeDisabled()
|
|
389
|
+
expect(label).toHaveClass('cursor-not-allowed', 'opacity-70')
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it('uses custom id when provided', () => {
|
|
393
|
+
render(
|
|
394
|
+
<RadioGroup>
|
|
395
|
+
<RadioItemWithLabel value="test" label="Test Option" id="custom-id" />
|
|
396
|
+
</RadioGroup>
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
400
|
+
const label = screen.getByText('Test Option')
|
|
401
|
+
|
|
402
|
+
expect(radioItem).toHaveAttribute('id', 'custom-id')
|
|
403
|
+
expect(label).toHaveAttribute('for', 'custom-id')
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it('generates unique id when not provided', () => {
|
|
407
|
+
render(
|
|
408
|
+
<RadioGroup>
|
|
409
|
+
<RadioItemWithLabel value="test1" label="Test Option 1" />
|
|
410
|
+
<RadioItemWithLabel value="test2" label="Test Option 2" />
|
|
411
|
+
</RadioGroup>
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
const radioItem1 = screen.getByDisplayValue('test1')
|
|
415
|
+
const radioItem2 = screen.getByDisplayValue('test2')
|
|
416
|
+
const label1 = screen.getByText('Test Option 1')
|
|
417
|
+
const label2 = screen.getByText('Test Option 2')
|
|
418
|
+
|
|
419
|
+
expect(radioItem1.id).not.toBe(radioItem2.id)
|
|
420
|
+
expect(label1.getAttribute('for')).toBe(radioItem1.id)
|
|
421
|
+
expect(label2.getAttribute('for')).toBe(radioItem2.id)
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it('maintains displayName', () => {
|
|
425
|
+
expect(RadioItemWithLabel.displayName).toBe('RadioItemWithLabel')
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
describe('Complex Combinations', () => {
|
|
430
|
+
it('renders complete radio group with all components', () => {
|
|
431
|
+
const onValueChange = jest.fn()
|
|
432
|
+
render(
|
|
433
|
+
<RadioGroup value="option2" onValueChange={onValueChange} data-testid="radio-group">
|
|
434
|
+
<RadioItemWithLabel value="option1" label="Option 1" />
|
|
435
|
+
<RadioItemWithLabel value="option2" label="Option 2" />
|
|
436
|
+
<div className="flex items-center">
|
|
437
|
+
<RadioGroupItem value="option3" />
|
|
438
|
+
<RadioLabel htmlFor="option3">Option 3</RadioLabel>
|
|
439
|
+
</div>
|
|
440
|
+
</RadioGroup>
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
const option1 = screen.getByDisplayValue('option1')
|
|
444
|
+
const option2 = screen.getByDisplayValue('option2')
|
|
445
|
+
const option3 = screen.getByDisplayValue('option3')
|
|
446
|
+
|
|
447
|
+
expect(option1).not.toBeChecked()
|
|
448
|
+
expect(option2).toBeChecked()
|
|
449
|
+
expect(option3).not.toBeChecked()
|
|
450
|
+
|
|
451
|
+
fireEvent.click(option3)
|
|
452
|
+
expect(onValueChange).toHaveBeenCalledWith('option3')
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('handles all variants and sizes together', () => {
|
|
456
|
+
render(
|
|
457
|
+
<RadioGroup data-testid="radio-group">
|
|
458
|
+
<RadioGroupItem value="default" variant="default" size="sm" />
|
|
459
|
+
<RadioGroupItem value="outline" variant="outline" size="md" />
|
|
460
|
+
<RadioGroupItem value="filled" variant="filled" size="lg" />
|
|
461
|
+
</RadioGroup>
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
const defaultItem = screen.getByDisplayValue('default').nextElementSibling
|
|
465
|
+
const outlineItem = screen.getByDisplayValue('outline').nextElementSibling
|
|
466
|
+
const filledItem = screen.getByDisplayValue('filled').nextElementSibling
|
|
467
|
+
|
|
468
|
+
expect(defaultItem).toHaveClass('h-3.5', 'w-3.5', 'border-border', 'bg-background')
|
|
469
|
+
expect(outlineItem).toHaveClass('h-5', 'w-5', 'border-border', 'bg-transparent')
|
|
470
|
+
expect(filledItem).toHaveClass('h-6', 'w-6', 'border-primary', 'bg-primary/10')
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
it('handles name attribute for form submission', () => {
|
|
474
|
+
render(
|
|
475
|
+
<RadioGroup name="test-group">
|
|
476
|
+
<RadioGroupItem value="option1" />
|
|
477
|
+
<RadioGroupItem value="option2" />
|
|
478
|
+
</RadioGroup>
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
const option1 = screen.getByDisplayValue('option1')
|
|
482
|
+
const option2 = screen.getByDisplayValue('option2')
|
|
483
|
+
|
|
484
|
+
expect(option1).toHaveAttribute('name', 'test-group')
|
|
485
|
+
expect(option2).toHaveAttribute('name', 'test-group')
|
|
486
|
+
})
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
describe('Edge Cases', () => {
|
|
490
|
+
it('handles empty radio group', () => {
|
|
491
|
+
render(<RadioGroup data-testid="radio-group" />)
|
|
492
|
+
|
|
493
|
+
const radioGroup = screen.getByTestId('radio-group')
|
|
494
|
+
expect(radioGroup).toBeInTheDocument()
|
|
495
|
+
expect(radioGroup).toBeEmptyDOMElement()
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
it('handles radio item without context', () => {
|
|
499
|
+
// RadioGroupItem outside of RadioGroup context
|
|
500
|
+
render(<RadioGroupItem value="test" />)
|
|
501
|
+
|
|
502
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
503
|
+
expect(radioItem).toBeInTheDocument()
|
|
504
|
+
expect(radioItem).not.toBeChecked()
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
it('handles null and undefined children', () => {
|
|
508
|
+
render(
|
|
509
|
+
<RadioGroup>
|
|
510
|
+
{null}
|
|
511
|
+
<RadioGroupItem value="test" />
|
|
512
|
+
{undefined}
|
|
513
|
+
</RadioGroup>
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
const radioItem = screen.getByDisplayValue('test')
|
|
517
|
+
expect(radioItem).toBeInTheDocument()
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
it('handles complex label content', () => {
|
|
521
|
+
render(
|
|
522
|
+
<RadioGroup>
|
|
523
|
+
<RadioItemWithLabel
|
|
524
|
+
value="complex"
|
|
525
|
+
label={
|
|
526
|
+
<div>
|
|
527
|
+
<strong>Complex Label</strong>
|
|
528
|
+
<p>With description</p>
|
|
529
|
+
</div>
|
|
530
|
+
}
|
|
531
|
+
/>
|
|
532
|
+
</RadioGroup>
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
expect(screen.getByText('Complex Label')).toBeInTheDocument()
|
|
536
|
+
expect(screen.getByText('With description')).toBeInTheDocument()
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
it('handles disabled radio group with individual enabled items', () => {
|
|
540
|
+
render(
|
|
541
|
+
<RadioGroup disabled>
|
|
542
|
+
<RadioGroupItem value="option1" />
|
|
543
|
+
<RadioGroupItem value="option2" disabled={false} />
|
|
544
|
+
</RadioGroup>
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
const option1 = screen.getByDisplayValue('option1')
|
|
548
|
+
const option2 = screen.getByDisplayValue('option2')
|
|
549
|
+
|
|
550
|
+
// Both should be disabled because group is disabled
|
|
551
|
+
expect(option1).toBeDisabled()
|
|
552
|
+
expect(option2).toBeDisabled()
|
|
553
|
+
})
|
|
554
|
+
})
|
|
555
|
+
})
|