@moontra/moonui 2.0.8 → 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.
Files changed (94) hide show
  1. package/package.json +9 -2
  2. package/src/__tests__/use-intersection-observer.test.tsx +216 -0
  3. package/src/__tests__/use-local-storage.test.tsx +174 -0
  4. package/src/__tests__/use-pro-access.test.tsx +183 -0
  5. package/src/components/ui/__tests__/accordion.test.tsx +571 -0
  6. package/src/components/ui/__tests__/alert.test.tsx +484 -0
  7. package/src/components/ui/__tests__/aspect-ratio.test.tsx +492 -0
  8. package/src/components/ui/__tests__/avatar.test.tsx +382 -0
  9. package/src/components/ui/__tests__/badge.test.tsx +364 -0
  10. package/src/components/ui/__tests__/breadcrumb.test.tsx +687 -0
  11. package/src/components/ui/__tests__/button.test.tsx +91 -0
  12. package/src/components/ui/__tests__/card.test.tsx +104 -0
  13. package/src/components/ui/__tests__/checkbox.test.tsx +591 -0
  14. package/src/components/ui/__tests__/collapsible.test.tsx +592 -0
  15. package/src/components/ui/__tests__/color-picker.test.tsx +365 -0
  16. package/src/components/ui/__tests__/command.test.tsx +677 -0
  17. package/src/components/ui/__tests__/data-table.test.tsx +256 -0
  18. package/src/components/ui/__tests__/date-picker.test.tsx +320 -0
  19. package/src/components/ui/__tests__/dialog.test.tsx +764 -0
  20. package/src/components/ui/__tests__/input.test.tsx +318 -0
  21. package/src/components/ui/__tests__/label.test.tsx +103 -0
  22. package/src/components/ui/__tests__/popover.test.tsx +637 -0
  23. package/src/components/ui/__tests__/progress.test.tsx +382 -0
  24. package/src/components/ui/__tests__/radio-group.test.tsx +555 -0
  25. package/src/components/ui/__tests__/select.test.tsx +705 -0
  26. package/src/components/ui/__tests__/skeleton.test.tsx +253 -0
  27. package/src/components/ui/__tests__/slider.test.tsx +493 -0
  28. package/src/components/ui/__tests__/switch.test.tsx +372 -0
  29. package/src/components/ui/__tests__/table.test.tsx +824 -0
  30. package/src/components/ui/__tests__/tabs.test.tsx +887 -0
  31. package/src/components/ui/__tests__/toast.test.tsx +458 -0
  32. package/src/components/ui/__tests__/tooltip.test.tsx +583 -0
  33. package/src/components/ui/accordion.tsx +63 -0
  34. package/src/components/ui/alert.tsx +130 -0
  35. package/src/components/ui/aspect-ratio.tsx +72 -0
  36. package/src/components/ui/avatar.tsx +135 -0
  37. package/src/components/ui/badge.tsx +225 -0
  38. package/src/components/ui/breadcrumb.tsx +207 -0
  39. package/src/components/ui/button.stories.tsx +162 -0
  40. package/src/components/ui/button.tsx +221 -0
  41. package/src/components/ui/calendar.tsx +262 -0
  42. package/src/components/ui/card.stories.tsx +208 -0
  43. package/src/components/ui/card.tsx +141 -0
  44. package/src/components/ui/checkbox.tsx +256 -0
  45. package/src/components/ui/collapsible.tsx +129 -0
  46. package/src/components/ui/color-picker.tsx +664 -0
  47. package/src/components/ui/command.tsx +218 -0
  48. package/src/components/ui/data-table.stories.tsx +509 -0
  49. package/src/components/ui/data-table.tsx +623 -0
  50. package/src/components/ui/date-picker.stories.tsx +321 -0
  51. package/src/components/ui/date-picker.tsx +603 -0
  52. package/src/components/ui/dialog.tsx +332 -0
  53. package/src/components/ui/dropdown-menu.tsx +200 -0
  54. package/src/components/ui/file-upload.tsx +400 -0
  55. package/src/components/ui/github-stars.tsx +201 -0
  56. package/src/components/ui/index.ts +12 -0
  57. package/src/components/ui/input.stories.tsx +255 -0
  58. package/src/components/ui/input.tsx +219 -0
  59. package/src/components/ui/label.tsx +26 -0
  60. package/src/components/ui/locked-component.tsx +215 -0
  61. package/src/components/ui/moon-logo.tsx +97 -0
  62. package/src/components/ui/pagination.tsx +116 -0
  63. package/src/components/ui/popover.tsx +183 -0
  64. package/src/components/ui/progress.tsx +182 -0
  65. package/src/components/ui/radio-group.tsx +243 -0
  66. package/src/components/ui/select.tsx +273 -0
  67. package/src/components/ui/separator.tsx +140 -0
  68. package/src/components/ui/simple-editor.tsx +652 -0
  69. package/src/components/ui/skeleton.tsx +351 -0
  70. package/src/components/ui/slider.tsx +351 -0
  71. package/src/components/ui/switch.tsx +83 -0
  72. package/src/components/ui/table.tsx +322 -0
  73. package/src/components/ui/tabs.tsx +195 -0
  74. package/src/components/ui/textarea.tsx +46 -0
  75. package/src/components/ui/toast.tsx +313 -0
  76. package/src/components/ui/toggle.tsx +47 -0
  77. package/src/components/ui/tooltip.tsx +152 -0
  78. package/src/docs/theme-system.md +1194 -0
  79. package/src/index.tsx +39 -0
  80. package/src/lib/micro-interactions.ts +255 -0
  81. package/src/lib/theme.tsx +398 -0
  82. package/src/lib/utils.ts +69 -0
  83. package/src/styles/design-system.css +365 -0
  84. package/src/styles/index.css +4 -0
  85. package/src/styles/tailwind.css +6 -0
  86. package/src/styles/tokens.css +453 -0
  87. package/src/use-intersection-observer.tsx +154 -0
  88. package/src/use-local-storage.tsx +71 -0
  89. package/src/use-paddle.ts +138 -0
  90. package/src/use-performance-optimizer.ts +379 -0
  91. package/src/use-pro-access.ts +141 -0
  92. package/src/use-scroll-animation.ts +221 -0
  93. package/src/use-subscription.ts +37 -0
  94. package/src/use-toast.ts +32 -0
@@ -0,0 +1,364 @@
1
+ import React from 'react'
2
+ import { render, screen, fireEvent } from '@testing-library/react'
3
+ import { Badge } from '../badge'
4
+
5
+ describe('Badge Component', () => {
6
+ it('renders correctly with default props', () => {
7
+ render(<Badge>Default Badge</Badge>)
8
+ const badge = screen.getByText('Default Badge').parentElement!
9
+ expect(badge).toBeInTheDocument()
10
+ expect(badge).toHaveClass('inline-flex')
11
+ expect(badge).toHaveClass('items-center')
12
+ expect(badge).toHaveClass('h-6') // md size
13
+ expect(badge).toHaveClass('rounded-full') // default radius
14
+ })
15
+
16
+ it('renders with custom text content', () => {
17
+ render(<Badge>Custom Text</Badge>)
18
+ expect(screen.getByText('Custom Text')).toBeInTheDocument()
19
+ })
20
+
21
+ it('applies custom className', () => {
22
+ render(<Badge className="custom-badge">Test</Badge>)
23
+ const badge = screen.getByText('Test').parentElement!
24
+ expect(badge).toHaveClass('custom-badge')
25
+ })
26
+
27
+ describe('Variants', () => {
28
+ it('renders primary variant correctly (default)', () => {
29
+ render(<Badge variant="primary">Primary</Badge>)
30
+ const badge = screen.getByText('Primary').parentElement!
31
+ expect(badge).toHaveClass('bg-primary')
32
+ expect(badge).toHaveClass('text-primary-foreground')
33
+ expect(badge).toHaveClass('border-transparent')
34
+ })
35
+
36
+ it('renders secondary variant correctly', () => {
37
+ render(<Badge variant="secondary">Secondary</Badge>)
38
+ const badge = screen.getByText('Secondary').parentElement!
39
+ expect(badge).toHaveClass('bg-secondary')
40
+ expect(badge).toHaveClass('text-secondary-foreground')
41
+ })
42
+
43
+ it('renders destructive variant correctly', () => {
44
+ render(<Badge variant="destructive">Destructive</Badge>)
45
+ const badge = screen.getByText('Destructive').parentElement!
46
+ expect(badge).toHaveClass('bg-error')
47
+ expect(badge).toHaveClass('text-white')
48
+ })
49
+
50
+ it('renders outline variant correctly', () => {
51
+ render(<Badge variant="outline">Outline</Badge>)
52
+ const badge = screen.getByText('Outline').parentElement!
53
+ expect(badge).toHaveClass('border-gray-200')
54
+ expect(badge).toHaveClass('bg-transparent')
55
+ })
56
+
57
+ it('renders success variant correctly', () => {
58
+ render(<Badge variant="success">Success</Badge>)
59
+ const badge = screen.getByText('Success').parentElement!
60
+ expect(badge).toHaveClass('bg-success')
61
+ expect(badge).toHaveClass('text-white')
62
+ })
63
+
64
+ it('renders warning variant correctly', () => {
65
+ render(<Badge variant="warning">Warning</Badge>)
66
+ const badge = screen.getByText('Warning').parentElement!
67
+ expect(badge).toHaveClass('bg-warning')
68
+ expect(badge).toHaveClass('text-white')
69
+ })
70
+
71
+ it('renders ghost variant correctly', () => {
72
+ render(<Badge variant="ghost">Ghost</Badge>)
73
+ const badge = screen.getByText('Ghost').parentElement!
74
+ expect(badge).toHaveClass('bg-transparent')
75
+ expect(badge).toHaveClass('text-foreground')
76
+ })
77
+ })
78
+
79
+ describe('Sizes', () => {
80
+ it('renders small size correctly', () => {
81
+ render(<Badge size="sm">Small</Badge>)
82
+ const badge = screen.getByText('Small').parentElement!
83
+ expect(badge).toHaveClass('h-5')
84
+ expect(badge).toHaveClass('px-2')
85
+ expect(badge).toHaveClass('text-xs')
86
+ })
87
+
88
+ it('renders medium size correctly (default)', () => {
89
+ render(<Badge size="md">Medium</Badge>)
90
+ const badge = screen.getByText('Medium').parentElement!
91
+ expect(badge).toHaveClass('h-6')
92
+ expect(badge).toHaveClass('px-2.5')
93
+ expect(badge).toHaveClass('text-xs')
94
+ })
95
+
96
+ it('renders large size correctly', () => {
97
+ render(<Badge size="lg">Large</Badge>)
98
+ const badge = screen.getByText('Large').parentElement!
99
+ expect(badge).toHaveClass('h-7')
100
+ expect(badge).toHaveClass('px-3')
101
+ expect(badge).toHaveClass('text-sm')
102
+ })
103
+ })
104
+
105
+ describe('Radius', () => {
106
+ it('renders default radius correctly', () => {
107
+ render(<Badge radius="default">Default Radius</Badge>)
108
+ const badge = screen.getByText('Default Radius').parentElement!
109
+ expect(badge).toHaveClass('rounded-full')
110
+ })
111
+
112
+ it('renders small radius correctly', () => {
113
+ render(<Badge radius="sm">Small Radius</Badge>)
114
+ const badge = screen.getByText('Small Radius').parentElement!
115
+ expect(badge).toHaveClass('rounded-md')
116
+ })
117
+
118
+ it('renders large radius correctly', () => {
119
+ render(<Badge radius="lg">Large Radius</Badge>)
120
+ const badge = screen.getByText('Large Radius').parentElement!
121
+ expect(badge).toHaveClass('rounded-xl')
122
+ })
123
+
124
+ it('renders no radius correctly', () => {
125
+ render(<Badge radius="none">No Radius</Badge>)
126
+ const badge = screen.getByText('No Radius').parentElement!
127
+ expect(badge).toHaveClass('rounded-none')
128
+ })
129
+ })
130
+
131
+ describe('Dot Feature', () => {
132
+ it('renders with dot when withDot is true', () => {
133
+ render(<Badge withDot>With Dot</Badge>)
134
+ const badge = screen.getByText('With Dot').parentElement!
135
+ const dot = badge.querySelector('span[aria-hidden="true"]')
136
+ expect(dot).toBeInTheDocument()
137
+ expect(dot).toHaveClass('h-2')
138
+ expect(dot).toHaveClass('w-2')
139
+ expect(dot).toHaveClass('rounded-full')
140
+ })
141
+
142
+ it('does not render dot when withDot is false', () => {
143
+ render(<Badge withDot={false}>Without Dot</Badge>)
144
+ const badge = screen.getByText('Without Dot').parentElement!
145
+ const dot = badge.querySelector('span[aria-hidden="true"]')
146
+ expect(dot).not.toBeInTheDocument()
147
+ })
148
+
149
+ it('renders dot with custom color', () => {
150
+ render(<Badge withDot dotColor="bg-red-500">Custom Dot</Badge>)
151
+ const badge = screen.getByText('Custom Dot').parentElement!
152
+ const dot = badge.querySelector('span[aria-hidden="true"]')
153
+ expect(dot).toHaveClass('bg-red-500')
154
+ })
155
+
156
+ it('renders dot with variant-specific default colors', () => {
157
+ const { rerender } = render(<Badge withDot variant="destructive">Destructive</Badge>)
158
+ let dot = screen.getByText('Destructive').parentElement!.querySelector('span[aria-hidden="true"]')
159
+ expect(dot).toHaveClass('bg-white')
160
+
161
+ rerender(<Badge withDot variant="success">Success</Badge>)
162
+ dot = screen.getByText('Success').parentElement!.querySelector('span[aria-hidden="true"]')
163
+ expect(dot).toHaveClass('bg-white')
164
+
165
+ rerender(<Badge withDot variant="primary">Primary</Badge>)
166
+ dot = screen.getByText('Primary').parentElement!.querySelector('span[aria-hidden="true"]')
167
+ expect(dot).toHaveClass('bg-primary-foreground')
168
+ })
169
+ })
170
+
171
+ describe('Icons', () => {
172
+ const TestIcon = () => <span data-testid="test-icon">Icon</span>
173
+
174
+ it('renders with left icon', () => {
175
+ render(<Badge leftIcon={<TestIcon />}>With Left Icon</Badge>)
176
+ expect(screen.getByTestId('test-icon')).toBeInTheDocument()
177
+ expect(screen.getByText('With Left Icon')).toBeInTheDocument()
178
+ })
179
+
180
+ it('renders with right icon', () => {
181
+ render(<Badge rightIcon={<TestIcon />}>With Right Icon</Badge>)
182
+ expect(screen.getByTestId('test-icon')).toBeInTheDocument()
183
+ expect(screen.getByText('With Right Icon')).toBeInTheDocument()
184
+ })
185
+
186
+ it('renders with both left and right icons', () => {
187
+ const LeftIcon = () => <span data-testid="left-icon">Left</span>
188
+ const RightIcon = () => <span data-testid="right-icon">Right</span>
189
+
190
+ render(
191
+ <Badge leftIcon={<LeftIcon />} rightIcon={<RightIcon />}>
192
+ Both Icons
193
+ </Badge>
194
+ )
195
+
196
+ expect(screen.getByTestId('left-icon')).toBeInTheDocument()
197
+ expect(screen.getByTestId('right-icon')).toBeInTheDocument()
198
+ expect(screen.getByText('Both Icons')).toBeInTheDocument()
199
+ })
200
+ })
201
+
202
+ describe('Removable Feature', () => {
203
+ it('renders remove button when removable is true and onRemove is provided', () => {
204
+ const onRemove = jest.fn()
205
+ render(<Badge removable onRemove={onRemove}>Removable</Badge>)
206
+
207
+ const removeButton = screen.getByRole('button', { name: 'Remove badge' })
208
+ expect(removeButton).toBeInTheDocument()
209
+ expect(removeButton).toHaveAttribute('aria-label', 'Remove badge')
210
+ })
211
+
212
+ it('does not render remove button when removable is false', () => {
213
+ const onRemove = jest.fn()
214
+ render(<Badge removable={false} onRemove={onRemove}>Not Removable</Badge>)
215
+
216
+ const removeButton = screen.queryByRole('button', { name: 'Remove badge' })
217
+ expect(removeButton).not.toBeInTheDocument()
218
+ })
219
+
220
+ it('does not render remove button when onRemove is not provided', () => {
221
+ render(<Badge removable>No Remove Handler</Badge>)
222
+
223
+ const removeButton = screen.queryByRole('button', { name: 'Remove badge' })
224
+ expect(removeButton).not.toBeInTheDocument()
225
+ })
226
+
227
+ it('calls onRemove when remove button is clicked', () => {
228
+ const onRemove = jest.fn()
229
+ render(<Badge removable onRemove={onRemove}>Removable</Badge>)
230
+
231
+ const removeButton = screen.getByRole('button', { name: 'Remove badge' })
232
+ fireEvent.click(removeButton)
233
+
234
+ expect(onRemove).toHaveBeenCalledTimes(1)
235
+ })
236
+
237
+ it('stops propagation when remove button is clicked', () => {
238
+ const onRemove = jest.fn()
239
+ const onBadgeClick = jest.fn()
240
+
241
+ render(
242
+ <Badge removable onRemove={onRemove} onClick={onBadgeClick}>
243
+ Removable
244
+ </Badge>
245
+ )
246
+
247
+ const removeButton = screen.getByRole('button', { name: 'Remove badge' })
248
+ fireEvent.click(removeButton)
249
+
250
+ expect(onRemove).toHaveBeenCalledTimes(1)
251
+ expect(onBadgeClick).not.toHaveBeenCalled()
252
+ })
253
+
254
+ it('sets data-removable attribute when removable is true', () => {
255
+ const onRemove = jest.fn()
256
+ render(<Badge removable onRemove={onRemove}>Removable</Badge>)
257
+
258
+ const badge = screen.getByText('Removable').parentElement!
259
+ expect(badge).toHaveAttribute('data-removable', '')
260
+ })
261
+ })
262
+
263
+ describe('Complex Combinations', () => {
264
+ it('renders with all features combined', () => {
265
+ const onRemove = jest.fn()
266
+ const LeftIcon = () => <span data-testid="left-icon">L</span>
267
+ const RightIcon = () => <span data-testid="right-icon">R</span>
268
+
269
+ render(
270
+ <Badge
271
+ variant="success"
272
+ size="lg"
273
+ radius="sm"
274
+ withDot
275
+ dotColor="bg-yellow-400"
276
+ leftIcon={<LeftIcon />}
277
+ rightIcon={<RightIcon />}
278
+ removable
279
+ onRemove={onRemove}
280
+ className="custom-badge"
281
+ >
282
+ Complex Badge
283
+ </Badge>
284
+ )
285
+
286
+ const badge = screen.getByText('Complex Badge').parentElement!
287
+
288
+ // Check variant, size, radius
289
+ expect(badge).toHaveClass('bg-success')
290
+ expect(badge).toHaveClass('h-7')
291
+ expect(badge).toHaveClass('rounded-md')
292
+ expect(badge).toHaveClass('custom-badge')
293
+
294
+ // Check dot
295
+ const dot = badge.querySelector('span[aria-hidden="true"]')
296
+ expect(dot).toHaveClass('bg-yellow-400')
297
+
298
+ // Check icons
299
+ expect(screen.getByTestId('left-icon')).toBeInTheDocument()
300
+ expect(screen.getByTestId('right-icon')).toBeInTheDocument()
301
+
302
+ // Check remove button
303
+ const removeButton = screen.getByRole('button', { name: 'Remove badge' })
304
+ expect(removeButton).toBeInTheDocument()
305
+
306
+ // Test remove functionality
307
+ fireEvent.click(removeButton)
308
+ expect(onRemove).toHaveBeenCalledTimes(1)
309
+ })
310
+ })
311
+
312
+ describe('Accessibility', () => {
313
+ it('has proper ARIA attributes for remove button', () => {
314
+ const onRemove = jest.fn()
315
+ render(<Badge removable onRemove={onRemove}>Accessible</Badge>)
316
+
317
+ const removeButton = screen.getByRole('button', { name: 'Remove badge' })
318
+ expect(removeButton).toHaveAttribute('aria-label', 'Remove badge')
319
+ expect(removeButton).toHaveAttribute('type', 'button')
320
+ })
321
+
322
+ it('has aria-hidden on dot element', () => {
323
+ render(<Badge withDot>With Dot</Badge>)
324
+ const dot = screen.getByText('With Dot').parentElement!.querySelector('span[aria-hidden="true"]')
325
+ expect(dot).toHaveAttribute('aria-hidden', 'true')
326
+ })
327
+
328
+ it('has aria-hidden on remove icon', () => {
329
+ const onRemove = jest.fn()
330
+ render(<Badge removable onRemove={onRemove}>Removable</Badge>)
331
+
332
+ const removeIcon = screen.getByRole('button').querySelector('svg')
333
+ expect(removeIcon).toHaveAttribute('aria-hidden', 'true')
334
+ })
335
+
336
+ it('truncates long text content', () => {
337
+ render(<Badge>Very long badge text that should be truncated</Badge>)
338
+ const textSpan = screen.getByText('Very long badge text that should be truncated')
339
+ expect(textSpan).toHaveClass('truncate')
340
+ })
341
+ })
342
+
343
+ describe('Event Handling', () => {
344
+ it('handles click events on badge', () => {
345
+ const onClick = jest.fn()
346
+ render(<Badge onClick={onClick}>Clickable</Badge>)
347
+
348
+ const badge = screen.getByText('Clickable').parentElement!
349
+ fireEvent.click(badge)
350
+
351
+ expect(onClick).toHaveBeenCalledTimes(1)
352
+ })
353
+
354
+ it('handles focus events', () => {
355
+ const onFocus = jest.fn()
356
+ render(<Badge onFocus={onFocus}>Focusable</Badge>)
357
+
358
+ const badge = screen.getByText('Focusable').parentElement!
359
+ fireEvent.focus(badge)
360
+
361
+ expect(onFocus).toHaveBeenCalledTimes(1)
362
+ })
363
+ })
364
+ })