@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,318 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { Input } from '../input'
|
|
4
|
+
import { Search, Eye, EyeOff } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
describe('Input Component', () => {
|
|
7
|
+
it('renders correctly with default props', () => {
|
|
8
|
+
render(<Input placeholder="Enter text" />)
|
|
9
|
+
const input = screen.getByPlaceholderText('Enter text')
|
|
10
|
+
expect(input).toBeInTheDocument()
|
|
11
|
+
expect(input).toHaveClass('h-10')
|
|
12
|
+
expect(input).toHaveClass('w-full')
|
|
13
|
+
expect(input).toHaveClass('text-sm')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('handles value changes', () => {
|
|
17
|
+
const handleChange = jest.fn()
|
|
18
|
+
render(<Input onChange={handleChange} />)
|
|
19
|
+
|
|
20
|
+
const input = screen.getByRole('textbox')
|
|
21
|
+
fireEvent.change(input, { target: { value: 'test value' } })
|
|
22
|
+
|
|
23
|
+
expect(handleChange).toHaveBeenCalled()
|
|
24
|
+
expect(input).toHaveValue('test value')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('handles different input types', () => {
|
|
28
|
+
const { rerender } = render(<Input type="password" data-testid="input" />)
|
|
29
|
+
expect(screen.getByTestId('input')).toHaveAttribute('type', 'password')
|
|
30
|
+
|
|
31
|
+
rerender(<Input type="email" data-testid="input" />)
|
|
32
|
+
expect(screen.getByTestId('input')).toHaveAttribute('type', 'email')
|
|
33
|
+
|
|
34
|
+
rerender(<Input type="number" data-testid="input" />)
|
|
35
|
+
expect(screen.getByTestId('input')).toHaveAttribute('type', 'number')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('handles disabled state', () => {
|
|
39
|
+
render(<Input disabled />)
|
|
40
|
+
const input = screen.getByRole('textbox')
|
|
41
|
+
expect(input).toBeDisabled()
|
|
42
|
+
expect(input).toHaveClass('disabled:cursor-not-allowed')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('applies custom className', () => {
|
|
46
|
+
render(<Input className="custom-input" />)
|
|
47
|
+
expect(screen.getByRole('textbox')).toHaveClass('custom-input')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('forwards ref correctly', () => {
|
|
51
|
+
const ref = jest.fn()
|
|
52
|
+
render(<Input ref={ref} />)
|
|
53
|
+
expect(ref).toHaveBeenCalled()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('handles focus and blur events', () => {
|
|
57
|
+
const handleFocus = jest.fn()
|
|
58
|
+
const handleBlur = jest.fn()
|
|
59
|
+
|
|
60
|
+
render(<Input onFocus={handleFocus} onBlur={handleBlur} />)
|
|
61
|
+
|
|
62
|
+
const input = screen.getByRole('textbox')
|
|
63
|
+
fireEvent.focus(input)
|
|
64
|
+
expect(handleFocus).toHaveBeenCalled()
|
|
65
|
+
|
|
66
|
+
fireEvent.blur(input)
|
|
67
|
+
expect(handleBlur).toHaveBeenCalled()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('supports controlled component pattern', () => {
|
|
71
|
+
const TestComponent = () => {
|
|
72
|
+
const [value, setValue] = React.useState('')
|
|
73
|
+
return (
|
|
74
|
+
<Input
|
|
75
|
+
value={value}
|
|
76
|
+
onChange={(e) => setValue(e.target.value)}
|
|
77
|
+
data-testid="controlled-input"
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
render(<TestComponent />)
|
|
83
|
+
const input = screen.getByTestId('controlled-input') as HTMLInputElement
|
|
84
|
+
|
|
85
|
+
fireEvent.change(input, { target: { value: 'controlled' } })
|
|
86
|
+
expect(input.value).toBe('controlled')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('Variants', () => {
|
|
90
|
+
it('renders standard variant correctly', () => {
|
|
91
|
+
render(<Input variant="standard" data-testid="input" />)
|
|
92
|
+
const input = screen.getByTestId('input')
|
|
93
|
+
expect(input).toHaveClass('border')
|
|
94
|
+
expect(input).toHaveClass('border-gray-300')
|
|
95
|
+
expect(input).toHaveClass('rounded-md')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('renders filled variant correctly', () => {
|
|
99
|
+
render(<Input variant="filled" data-testid="input" />)
|
|
100
|
+
const input = screen.getByTestId('input')
|
|
101
|
+
expect(input).toHaveClass('bg-gray-100')
|
|
102
|
+
expect(input).toHaveClass('border-transparent')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('renders ghost variant correctly', () => {
|
|
106
|
+
render(<Input variant="ghost" data-testid="input" />)
|
|
107
|
+
const input = screen.getByTestId('input')
|
|
108
|
+
expect(input).toHaveClass('border-none')
|
|
109
|
+
expect(input).toHaveClass('bg-transparent')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('renders underline variant correctly', () => {
|
|
113
|
+
render(<Input variant="underline" data-testid="input" />)
|
|
114
|
+
const input = screen.getByTestId('input')
|
|
115
|
+
expect(input).toHaveClass('border-b')
|
|
116
|
+
expect(input).toHaveClass('rounded-none')
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('Sizes', () => {
|
|
121
|
+
it('renders small size correctly', () => {
|
|
122
|
+
render(<Input size="sm" data-testid="input" />)
|
|
123
|
+
const input = screen.getByTestId('input')
|
|
124
|
+
expect(input).toHaveClass('h-8')
|
|
125
|
+
expect(input).toHaveClass('text-xs')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('renders medium size correctly (default)', () => {
|
|
129
|
+
render(<Input data-testid="input" />)
|
|
130
|
+
const input = screen.getByTestId('input')
|
|
131
|
+
expect(input).toHaveClass('h-10')
|
|
132
|
+
expect(input).toHaveClass('text-sm')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('renders large size correctly', () => {
|
|
136
|
+
render(<Input size="lg" data-testid="input" />)
|
|
137
|
+
const input = screen.getByTestId('input')
|
|
138
|
+
expect(input).toHaveClass('h-12')
|
|
139
|
+
expect(input).toHaveClass('text-base')
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('Icons and Buttons', () => {
|
|
144
|
+
it('renders with left icon', () => {
|
|
145
|
+
render(<Input leftIcon={<Search data-testid="search-icon" />} />)
|
|
146
|
+
expect(screen.getByTestId('search-icon')).toBeInTheDocument()
|
|
147
|
+
expect(screen.getByRole('textbox')).toHaveClass('pl-10')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('renders with right icon', () => {
|
|
151
|
+
render(<Input rightIcon={<Search data-testid="search-icon" />} />)
|
|
152
|
+
expect(screen.getByTestId('search-icon')).toBeInTheDocument()
|
|
153
|
+
expect(screen.getByRole('textbox')).toHaveClass('pr-10')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('renders with right button', () => {
|
|
157
|
+
const button = <button data-testid="toggle-btn">Toggle</button>
|
|
158
|
+
render(<Input rightButton={button} />)
|
|
159
|
+
expect(screen.getByTestId('toggle-btn')).toBeInTheDocument()
|
|
160
|
+
expect(screen.getByRole('textbox')).toHaveClass('pr-10')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('handles password toggle functionality', () => {
|
|
164
|
+
const PasswordInput = () => {
|
|
165
|
+
const [showPassword, setShowPassword] = React.useState(false)
|
|
166
|
+
return (
|
|
167
|
+
<Input
|
|
168
|
+
type={showPassword ? 'text' : 'password'}
|
|
169
|
+
rightButton={
|
|
170
|
+
<button
|
|
171
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
172
|
+
data-testid="password-toggle"
|
|
173
|
+
>
|
|
174
|
+
{showPassword ? <EyeOff /> : <Eye />}
|
|
175
|
+
</button>
|
|
176
|
+
}
|
|
177
|
+
/>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
render(<PasswordInput />)
|
|
182
|
+
const input = screen.getByDisplayValue('') // Get input by display value
|
|
183
|
+
const toggleBtn = screen.getByTestId('password-toggle')
|
|
184
|
+
|
|
185
|
+
// Initially should be password type
|
|
186
|
+
expect(input).toHaveAttribute('type', 'password')
|
|
187
|
+
fireEvent.click(toggleBtn)
|
|
188
|
+
expect(input).toHaveAttribute('type', 'text')
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('Loading State', () => {
|
|
193
|
+
it('shows loading spinner when loading is true', () => {
|
|
194
|
+
render(<Input loading />)
|
|
195
|
+
expect(screen.getByRole('textbox')).toBeDisabled()
|
|
196
|
+
expect(screen.getByRole('textbox')).toHaveAttribute('data-loading', '')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('shows loading spinner with left icon', () => {
|
|
200
|
+
render(<Input loading leftIcon={<Search />} />)
|
|
201
|
+
const input = screen.getByRole('textbox')
|
|
202
|
+
expect(input).toBeDisabled()
|
|
203
|
+
expect(input).toHaveClass('pl-10')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('shows loading spinner without left icon', () => {
|
|
207
|
+
render(<Input loading />)
|
|
208
|
+
const input = screen.getByRole('textbox')
|
|
209
|
+
expect(input).toBeDisabled()
|
|
210
|
+
expect(input).toHaveClass('pl-10')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('hides right icon when loading', () => {
|
|
214
|
+
render(<Input loading rightIcon={<Search data-testid="search-icon" />} />)
|
|
215
|
+
expect(screen.queryByTestId('search-icon')).not.toBeInTheDocument()
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
describe('Error and Success States', () => {
|
|
220
|
+
it('displays error message', () => {
|
|
221
|
+
render(<Input error="This field is required" id="test-input" />)
|
|
222
|
+
const input = screen.getByRole('textbox')
|
|
223
|
+
const errorMessage = screen.getByText('This field is required')
|
|
224
|
+
|
|
225
|
+
expect(input).toHaveAttribute('aria-invalid', 'true')
|
|
226
|
+
expect(input).toHaveAttribute('aria-describedby', 'test-input-error')
|
|
227
|
+
expect(input).toHaveAttribute('data-error', '')
|
|
228
|
+
expect(input).toHaveClass('border-error')
|
|
229
|
+
expect(errorMessage).toHaveClass('text-error')
|
|
230
|
+
expect(errorMessage).toHaveAttribute('id', 'test-input-error')
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('displays success message', () => {
|
|
234
|
+
render(<Input success="Valid input" id="test-input" />)
|
|
235
|
+
const input = screen.getByRole('textbox')
|
|
236
|
+
const successMessage = screen.getByText('Valid input')
|
|
237
|
+
|
|
238
|
+
expect(input).toHaveAttribute('aria-describedby', 'test-input-success')
|
|
239
|
+
expect(input).toHaveAttribute('data-success', '')
|
|
240
|
+
expect(input).toHaveClass('border-success')
|
|
241
|
+
expect(successMessage).toHaveClass('text-success')
|
|
242
|
+
expect(successMessage).toHaveAttribute('id', 'test-input-success')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('prioritizes error over success', () => {
|
|
246
|
+
render(<Input error="Error message" success="Success message" />)
|
|
247
|
+
expect(screen.getByText('Error message')).toBeInTheDocument()
|
|
248
|
+
expect(screen.queryByText('Success message')).not.toBeInTheDocument()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('shows message when alwaysShowMessage is true', () => {
|
|
252
|
+
render(<Input alwaysShowMessage success="Test message" />)
|
|
253
|
+
// Should show message container with success message
|
|
254
|
+
const messageContainer = screen.getByText('Test message')
|
|
255
|
+
expect(messageContainer).toBeInTheDocument()
|
|
256
|
+
expect(messageContainer).toHaveClass('text-success')
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('applies custom message className', () => {
|
|
260
|
+
render(<Input error="Error" messageClassName="custom-message" />)
|
|
261
|
+
const message = screen.getByText('Error')
|
|
262
|
+
expect(message).toHaveClass('custom-message')
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
describe('Custom Styling', () => {
|
|
267
|
+
it('applies custom wrapper className', () => {
|
|
268
|
+
render(
|
|
269
|
+
<Input
|
|
270
|
+
wrapperClassName="custom-wrapper"
|
|
271
|
+
data-testid="input"
|
|
272
|
+
/>
|
|
273
|
+
)
|
|
274
|
+
const wrapper = screen.getByTestId('input').parentElement
|
|
275
|
+
expect(wrapper).toHaveClass('custom-wrapper')
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('combines multiple class names correctly', () => {
|
|
279
|
+
render(
|
|
280
|
+
<Input
|
|
281
|
+
className="custom-input"
|
|
282
|
+
variant="filled"
|
|
283
|
+
size="lg"
|
|
284
|
+
error="Error message"
|
|
285
|
+
data-testid="input"
|
|
286
|
+
/>
|
|
287
|
+
)
|
|
288
|
+
const input = screen.getByTestId('input')
|
|
289
|
+
expect(input).toHaveClass('custom-input')
|
|
290
|
+
expect(input).toHaveClass('bg-gray-100')
|
|
291
|
+
expect(input).toHaveClass('h-12')
|
|
292
|
+
expect(input).toHaveClass('border-error')
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('Accessibility', () => {
|
|
297
|
+
it('has proper ARIA attributes for error state', () => {
|
|
298
|
+
render(<Input error="Required field" id="accessible-input" />)
|
|
299
|
+
const input = screen.getByRole('textbox')
|
|
300
|
+
|
|
301
|
+
expect(input).toHaveAttribute('aria-invalid', 'true')
|
|
302
|
+
expect(input).toHaveAttribute('aria-describedby', 'accessible-input-error')
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('has proper ARIA attributes for success state', () => {
|
|
306
|
+
render(<Input success="Valid input" id="accessible-input" />)
|
|
307
|
+
const input = screen.getByRole('textbox')
|
|
308
|
+
|
|
309
|
+
expect(input).toHaveAttribute('aria-describedby', 'accessible-input-success')
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('has proper loading indicators', () => {
|
|
313
|
+
render(<Input loading />)
|
|
314
|
+
const spinner = screen.getByRole('textbox').parentElement?.querySelector('[aria-hidden="true"]')
|
|
315
|
+
expect(spinner).toBeInTheDocument()
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import '@testing-library/jest-dom'
|
|
4
|
+
import { Label } from '../label'
|
|
5
|
+
|
|
6
|
+
describe('Label Component', () => {
|
|
7
|
+
it('renders correctly with default props', () => {
|
|
8
|
+
render(<Label>Default Label</Label>)
|
|
9
|
+
const label = screen.getByText('Default Label')
|
|
10
|
+
expect(label).toBeInTheDocument()
|
|
11
|
+
expect(label).toHaveClass('text-sm')
|
|
12
|
+
expect(label).toHaveClass('font-medium')
|
|
13
|
+
expect(label).toHaveClass('leading-none')
|
|
14
|
+
expect(label).toHaveClass('text-gray-900')
|
|
15
|
+
expect(label).toHaveClass('dark:text-gray-200')
|
|
16
|
+
expect(label).toHaveClass('peer-disabled:cursor-not-allowed')
|
|
17
|
+
expect(label).toHaveClass('peer-disabled:opacity-70')
|
|
18
|
+
expect(label).toHaveClass('dark:peer-disabled:opacity-60')
|
|
19
|
+
expect(label).toHaveClass('transition-colors')
|
|
20
|
+
expect(label).toHaveClass('duration-200')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('renders with custom text content', () => {
|
|
24
|
+
render(<Label>Custom Label Text</Label>)
|
|
25
|
+
const label = screen.getByText('Custom Label Text')
|
|
26
|
+
expect(label).toBeInTheDocument()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('applies custom className', () => {
|
|
30
|
+
render(<Label className="custom-label">Test Label</Label>)
|
|
31
|
+
const label = screen.getByText('Test Label')
|
|
32
|
+
expect(label).toHaveClass('custom-label')
|
|
33
|
+
// Should still have default classes
|
|
34
|
+
expect(label).toHaveClass('text-sm')
|
|
35
|
+
expect(label).toHaveClass('font-medium')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('forwards ref correctly', () => {
|
|
39
|
+
const ref = React.createRef<HTMLLabelElement>()
|
|
40
|
+
render(<Label ref={ref}>Ref Label</Label>)
|
|
41
|
+
expect(ref.current).toBeInstanceOf(HTMLLabelElement)
|
|
42
|
+
expect(ref.current).toHaveTextContent('Ref Label')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('passes through HTML label props', () => {
|
|
46
|
+
render(<Label htmlFor="test-input" id="test-label">Form Label</Label>)
|
|
47
|
+
const label = screen.getByText('Form Label')
|
|
48
|
+
expect(label).toHaveAttribute('for', 'test-input')
|
|
49
|
+
expect(label).toHaveAttribute('id', 'test-label')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('handles onClick events', () => {
|
|
53
|
+
const onClick = jest.fn()
|
|
54
|
+
render(<Label onClick={onClick}>Clickable Label</Label>)
|
|
55
|
+
const label = screen.getByText('Clickable Label')
|
|
56
|
+
label.click()
|
|
57
|
+
expect(onClick).toHaveBeenCalledTimes(1)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('renders as a label element', () => {
|
|
61
|
+
render(<Label>Label Element</Label>)
|
|
62
|
+
const label = screen.getByText('Label Element')
|
|
63
|
+
expect(label.tagName).toBe('LABEL')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('supports data attributes', () => {
|
|
67
|
+
render(<Label data-testid="custom-label" data-custom="value">Data Label</Label>)
|
|
68
|
+
const label = screen.getByTestId('custom-label')
|
|
69
|
+
expect(label).toHaveAttribute('data-custom', 'value')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('supports aria attributes', () => {
|
|
73
|
+
render(<Label aria-label="Accessible Label" aria-describedby="description">ARIA Label</Label>)
|
|
74
|
+
const label = screen.getByText('ARIA Label')
|
|
75
|
+
expect(label).toHaveAttribute('aria-label', 'Accessible Label')
|
|
76
|
+
expect(label).toHaveAttribute('aria-describedby', 'description')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('handles empty content', () => {
|
|
80
|
+
render(<Label data-testid="empty-label"></Label>)
|
|
81
|
+
const label = screen.getByTestId('empty-label')
|
|
82
|
+
expect(label).toBeInTheDocument()
|
|
83
|
+
expect(label).toHaveClass('text-sm')
|
|
84
|
+
expect(label.tagName).toBe('LABEL')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('handles children as React nodes', () => {
|
|
88
|
+
render(
|
|
89
|
+
<Label>
|
|
90
|
+
<span>Child</span> Label
|
|
91
|
+
</Label>
|
|
92
|
+
)
|
|
93
|
+
const label = screen.getByText((content, element) => {
|
|
94
|
+
return element?.tagName === 'LABEL' && element.textContent === 'Child Label'
|
|
95
|
+
})
|
|
96
|
+
expect(label).toBeInTheDocument()
|
|
97
|
+
expect(label.querySelector('span')).toHaveTextContent('Child')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('maintains displayName from Radix primitive', () => {
|
|
101
|
+
expect(Label.displayName).toBeDefined()
|
|
102
|
+
})
|
|
103
|
+
})
|