@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,764 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import '@testing-library/jest-dom'
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogTrigger,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogFooter,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogClose,
|
|
13
|
+
DialogForm,
|
|
14
|
+
} from '../dialog'
|
|
15
|
+
|
|
16
|
+
// Mock Radix UI Dialog components
|
|
17
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
18
|
+
jest.mock('@radix-ui/react-dialog', () => {
|
|
19
|
+
const mockForwardRef = (component: any) => {
|
|
20
|
+
const forwardedComponent = (props: any, ref: any) => component({ ...props, ref })
|
|
21
|
+
forwardedComponent.displayName = component.displayName || component.name
|
|
22
|
+
return forwardedComponent
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
Root: ({ children, open, ...props }: any) => (
|
|
27
|
+
<div data-testid="dialog-root" data-open={open} {...props}>
|
|
28
|
+
{children}
|
|
29
|
+
</div>
|
|
30
|
+
),
|
|
31
|
+
Trigger: ({ children, ...props }: any) => (
|
|
32
|
+
<button data-testid="dialog-trigger" {...props}>
|
|
33
|
+
{children}
|
|
34
|
+
</button>
|
|
35
|
+
),
|
|
36
|
+
Portal: ({ children }: any) => (
|
|
37
|
+
<div data-testid="dialog-portal">{children}</div>
|
|
38
|
+
),
|
|
39
|
+
Overlay: mockForwardRef(({ className, ref, ...props }: any) => (
|
|
40
|
+
<div
|
|
41
|
+
ref={ref}
|
|
42
|
+
data-testid="dialog-overlay"
|
|
43
|
+
className={className}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)),
|
|
47
|
+
Content: mockForwardRef(({ className, children, ref, ...props }: any) => (
|
|
48
|
+
<div
|
|
49
|
+
ref={ref}
|
|
50
|
+
data-testid="dialog-content"
|
|
51
|
+
className={className}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</div>
|
|
56
|
+
)),
|
|
57
|
+
Close: ({ children, onClick, ...props }: any) => (
|
|
58
|
+
<button data-testid="dialog-close" onClick={onClick} {...props}>
|
|
59
|
+
{children}
|
|
60
|
+
</button>
|
|
61
|
+
),
|
|
62
|
+
Title: mockForwardRef(({ className, children, ref, ...props }: any) => (
|
|
63
|
+
<h2 ref={ref} data-testid="dialog-title" className={className} {...props}>
|
|
64
|
+
{children}
|
|
65
|
+
</h2>
|
|
66
|
+
)),
|
|
67
|
+
Description: mockForwardRef(({ className, children, ref, ...props }: any) => (
|
|
68
|
+
<p ref={ref} data-testid="dialog-description" className={className} {...props}>
|
|
69
|
+
{children}
|
|
70
|
+
</p>
|
|
71
|
+
)),
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Mock Lucide React icons
|
|
76
|
+
jest.mock('lucide-react', () => ({
|
|
77
|
+
X: ({ className, ...props }: any) => (
|
|
78
|
+
<svg data-testid="x-icon" className={className} {...props}>
|
|
79
|
+
<path d="x" />
|
|
80
|
+
</svg>
|
|
81
|
+
),
|
|
82
|
+
Check: ({ className, ...props }: any) => (
|
|
83
|
+
<svg data-testid="check-icon" className={className} {...props}>
|
|
84
|
+
<path d="check" />
|
|
85
|
+
</svg>
|
|
86
|
+
),
|
|
87
|
+
Loader2: ({ className, ...props }: any) => (
|
|
88
|
+
<svg data-testid="loader-icon" className={className} {...props}>
|
|
89
|
+
<path d="loader" />
|
|
90
|
+
</svg>
|
|
91
|
+
),
|
|
92
|
+
}))
|
|
93
|
+
|
|
94
|
+
describe('Dialog Components', () => {
|
|
95
|
+
describe('Dialog Root Component', () => {
|
|
96
|
+
it('renders correctly', () => {
|
|
97
|
+
render(
|
|
98
|
+
<Dialog>
|
|
99
|
+
<DialogTrigger>Open Dialog</DialogTrigger>
|
|
100
|
+
<DialogContent>Dialog Content</DialogContent>
|
|
101
|
+
</Dialog>
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
expect(screen.getByTestId('dialog-root')).toBeInTheDocument()
|
|
105
|
+
expect(screen.getByTestId('dialog-trigger')).toBeInTheDocument()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('handles open state', () => {
|
|
109
|
+
render(
|
|
110
|
+
<Dialog open={true}>
|
|
111
|
+
<DialogContent>Dialog Content</DialogContent>
|
|
112
|
+
</Dialog>
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const root = screen.getByTestId('dialog-root')
|
|
116
|
+
expect(root).toHaveAttribute('data-open', 'true')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('handles onOpenChange callback', () => {
|
|
120
|
+
const onOpenChange = jest.fn()
|
|
121
|
+
render(
|
|
122
|
+
<Dialog onOpenChange={onOpenChange}>
|
|
123
|
+
<DialogTrigger>Open Dialog</DialogTrigger>
|
|
124
|
+
</Dialog>
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
expect(screen.getByTestId('dialog-root')).toBeInTheDocument()
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('DialogTrigger Component', () => {
|
|
132
|
+
it('renders correctly', () => {
|
|
133
|
+
render(
|
|
134
|
+
<Dialog>
|
|
135
|
+
<DialogTrigger>Open Dialog</DialogTrigger>
|
|
136
|
+
</Dialog>
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
const trigger = screen.getByTestId('dialog-trigger')
|
|
140
|
+
expect(trigger).toBeInTheDocument()
|
|
141
|
+
expect(trigger).toHaveTextContent('Open Dialog')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('passes through HTML attributes', () => {
|
|
145
|
+
render(
|
|
146
|
+
<Dialog>
|
|
147
|
+
<DialogTrigger id="trigger-1" className="custom-trigger">
|
|
148
|
+
Open Dialog
|
|
149
|
+
</DialogTrigger>
|
|
150
|
+
</Dialog>
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
const trigger = screen.getByTestId('dialog-trigger')
|
|
154
|
+
expect(trigger).toHaveAttribute('id', 'trigger-1')
|
|
155
|
+
expect(trigger).toHaveClass('custom-trigger')
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
describe('DialogContent Component', () => {
|
|
160
|
+
it('renders correctly with default props', () => {
|
|
161
|
+
render(
|
|
162
|
+
<Dialog>
|
|
163
|
+
<DialogContent data-testid="content">Dialog Content</DialogContent>
|
|
164
|
+
</Dialog>
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
const content = screen.getByTestId('content')
|
|
168
|
+
expect(content).toBeInTheDocument()
|
|
169
|
+
expect(content).toHaveTextContent('Dialog Content')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('applies custom className', () => {
|
|
173
|
+
render(
|
|
174
|
+
<Dialog>
|
|
175
|
+
<DialogContent className="custom-dialog" data-testid="content">
|
|
176
|
+
Content
|
|
177
|
+
</DialogContent>
|
|
178
|
+
</Dialog>
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const content = screen.getByTestId('content')
|
|
182
|
+
expect(content).toHaveClass('custom-dialog')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('forwards ref correctly', () => {
|
|
186
|
+
const ref = React.createRef<HTMLDivElement>()
|
|
187
|
+
render(
|
|
188
|
+
<Dialog>
|
|
189
|
+
<DialogContent ref={ref} data-testid="content">
|
|
190
|
+
Content
|
|
191
|
+
</DialogContent>
|
|
192
|
+
</Dialog>
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
const content = screen.getByTestId('content')
|
|
196
|
+
expect(content).toBeInTheDocument()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('renders default variant correctly', () => {
|
|
200
|
+
render(
|
|
201
|
+
<Dialog>
|
|
202
|
+
<DialogContent data-testid="content">Content</DialogContent>
|
|
203
|
+
</Dialog>
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
const content = screen.getByTestId('content')
|
|
207
|
+
expect(content).toHaveClass('border-gray-200 dark:border-gray-700')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('renders primary variant correctly', () => {
|
|
211
|
+
render(
|
|
212
|
+
<Dialog>
|
|
213
|
+
<DialogContent variant="primary" data-testid="content">
|
|
214
|
+
Content
|
|
215
|
+
</DialogContent>
|
|
216
|
+
</Dialog>
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
const content = screen.getByTestId('content')
|
|
220
|
+
expect(content).toHaveClass('border-primary/20 dark:border-primary/30')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('renders ghost variant correctly', () => {
|
|
224
|
+
render(
|
|
225
|
+
<Dialog>
|
|
226
|
+
<DialogContent variant="ghost" data-testid="content">
|
|
227
|
+
Content
|
|
228
|
+
</DialogContent>
|
|
229
|
+
</Dialog>
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
const content = screen.getByTestId('content')
|
|
233
|
+
expect(content).toHaveClass('border-transparent shadow-xl')
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('renders different sizes correctly', () => {
|
|
237
|
+
const { rerender } = render(
|
|
238
|
+
<Dialog>
|
|
239
|
+
<DialogContent size="sm" data-testid="content">
|
|
240
|
+
Content
|
|
241
|
+
</DialogContent>
|
|
242
|
+
</Dialog>
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
expect(screen.getByTestId('content')).toHaveClass('max-w-sm p-5')
|
|
246
|
+
|
|
247
|
+
rerender(
|
|
248
|
+
<Dialog>
|
|
249
|
+
<DialogContent size="lg" data-testid="content">
|
|
250
|
+
Content
|
|
251
|
+
</DialogContent>
|
|
252
|
+
</Dialog>
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
expect(screen.getByTestId('content')).toHaveClass('max-w-2xl p-7')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('renders different radius variants', () => {
|
|
259
|
+
render(
|
|
260
|
+
<Dialog>
|
|
261
|
+
<DialogContent radius="xl" data-testid="content">
|
|
262
|
+
Content
|
|
263
|
+
</DialogContent>
|
|
264
|
+
</Dialog>
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const content = screen.getByTestId('content')
|
|
268
|
+
expect(content).toHaveClass('rounded-2xl')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('renders with title and description', () => {
|
|
272
|
+
render(
|
|
273
|
+
<Dialog>
|
|
274
|
+
<DialogContent title="Dialog Title" description="Dialog Description">
|
|
275
|
+
Content
|
|
276
|
+
</DialogContent>
|
|
277
|
+
</Dialog>
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
expect(screen.getByTestId('dialog-title')).toHaveTextContent('Dialog Title')
|
|
281
|
+
expect(screen.getByTestId('dialog-description')).toHaveTextContent('Dialog Description')
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('renders with custom icon', () => {
|
|
285
|
+
const CustomIcon = () => <span data-testid="custom-icon">Icon</span>
|
|
286
|
+
render(
|
|
287
|
+
<Dialog>
|
|
288
|
+
<DialogContent icon={<CustomIcon />} title="Title">
|
|
289
|
+
Content
|
|
290
|
+
</DialogContent>
|
|
291
|
+
</Dialog>
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
expect(screen.getByTestId('custom-icon')).toBeInTheDocument()
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('renders loading state', () => {
|
|
298
|
+
render(
|
|
299
|
+
<Dialog>
|
|
300
|
+
<DialogContent loading={true} title="Loading">
|
|
301
|
+
Content
|
|
302
|
+
</DialogContent>
|
|
303
|
+
</Dialog>
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
expect(screen.getByTestId('loader-icon')).toBeInTheDocument()
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('renders success state', () => {
|
|
310
|
+
render(
|
|
311
|
+
<Dialog>
|
|
312
|
+
<DialogContent success={true} title="Success">
|
|
313
|
+
Content
|
|
314
|
+
</DialogContent>
|
|
315
|
+
</Dialog>
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
expect(screen.getByTestId('check-icon')).toBeInTheDocument()
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('hides close button when hideCloseButton is true', () => {
|
|
322
|
+
render(
|
|
323
|
+
<Dialog>
|
|
324
|
+
<DialogContent hideCloseButton={true}>
|
|
325
|
+
Content
|
|
326
|
+
</DialogContent>
|
|
327
|
+
</Dialog>
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
expect(screen.queryByTestId('dialog-close')).not.toBeInTheDocument()
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('shows close button by default', () => {
|
|
334
|
+
render(
|
|
335
|
+
<Dialog>
|
|
336
|
+
<DialogContent>Content</DialogContent>
|
|
337
|
+
</Dialog>
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
expect(screen.getByTestId('dialog-close')).toBeInTheDocument()
|
|
341
|
+
expect(screen.getByTestId('x-icon')).toBeInTheDocument()
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('calls onClose when close button is clicked', () => {
|
|
345
|
+
const onClose = jest.fn()
|
|
346
|
+
render(
|
|
347
|
+
<Dialog>
|
|
348
|
+
<DialogContent onClose={onClose}>Content</DialogContent>
|
|
349
|
+
</Dialog>
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
const closeButton = screen.getByTestId('dialog-close')
|
|
353
|
+
fireEvent.click(closeButton)
|
|
354
|
+
expect(onClose).toHaveBeenCalledTimes(1)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('renders different positions correctly', () => {
|
|
358
|
+
render(
|
|
359
|
+
<Dialog>
|
|
360
|
+
<DialogContent position="top" data-testid="content">
|
|
361
|
+
Content
|
|
362
|
+
</DialogContent>
|
|
363
|
+
</Dialog>
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
const content = screen.getByTestId('content')
|
|
367
|
+
expect(content).toHaveClass('top-[5%]')
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('passes through HTML attributes', () => {
|
|
371
|
+
render(
|
|
372
|
+
<Dialog>
|
|
373
|
+
<DialogContent id="dialog-1" data-custom="value" data-testid="content">
|
|
374
|
+
Content
|
|
375
|
+
</DialogContent>
|
|
376
|
+
</Dialog>
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
const content = screen.getByTestId('content')
|
|
380
|
+
expect(content).toHaveAttribute('id', 'dialog-1')
|
|
381
|
+
expect(content).toHaveAttribute('data-custom', 'value')
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
describe('DialogHeader Component', () => {
|
|
386
|
+
it('renders correctly', () => {
|
|
387
|
+
render(<DialogHeader data-testid="dialog-header">Header Content</DialogHeader>)
|
|
388
|
+
|
|
389
|
+
const header = screen.getByTestId('dialog-header')
|
|
390
|
+
expect(header).toBeInTheDocument()
|
|
391
|
+
expect(header).toHaveTextContent('Header Content')
|
|
392
|
+
expect(header).toHaveClass('flex flex-col space-y-2 text-center sm:text-left')
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it('applies custom className', () => {
|
|
396
|
+
render(
|
|
397
|
+
<DialogHeader className="custom-header" data-testid="dialog-header">
|
|
398
|
+
Header
|
|
399
|
+
</DialogHeader>
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
const header = screen.getByTestId('dialog-header')
|
|
403
|
+
expect(header).toHaveClass('custom-header')
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it('passes through HTML attributes', () => {
|
|
407
|
+
render(
|
|
408
|
+
<DialogHeader id="header-1" data-testid="dialog-header">
|
|
409
|
+
Header
|
|
410
|
+
</DialogHeader>
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
const header = screen.getByTestId('dialog-header')
|
|
414
|
+
expect(header).toHaveAttribute('id', 'header-1')
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
it('maintains displayName', () => {
|
|
418
|
+
expect(DialogHeader.displayName).toBe('DialogHeader')
|
|
419
|
+
})
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
describe('DialogFooter Component', () => {
|
|
423
|
+
it('renders correctly', () => {
|
|
424
|
+
render(<DialogFooter data-testid="dialog-footer">Footer Content</DialogFooter>)
|
|
425
|
+
|
|
426
|
+
const footer = screen.getByTestId('dialog-footer')
|
|
427
|
+
expect(footer).toBeInTheDocument()
|
|
428
|
+
expect(footer).toHaveTextContent('Footer Content')
|
|
429
|
+
expect(footer).toHaveClass('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2 mt-6')
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('applies custom className', () => {
|
|
433
|
+
render(
|
|
434
|
+
<DialogFooter className="custom-footer" data-testid="dialog-footer">
|
|
435
|
+
Footer
|
|
436
|
+
</DialogFooter>
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
const footer = screen.getByTestId('dialog-footer')
|
|
440
|
+
expect(footer).toHaveClass('custom-footer')
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('passes through HTML attributes', () => {
|
|
444
|
+
render(
|
|
445
|
+
<DialogFooter id="footer-1" data-testid="dialog-footer">
|
|
446
|
+
Footer
|
|
447
|
+
</DialogFooter>
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
const footer = screen.getByTestId('dialog-footer')
|
|
451
|
+
expect(footer).toHaveAttribute('id', 'footer-1')
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
it('maintains displayName', () => {
|
|
455
|
+
expect(DialogFooter.displayName).toBe('DialogFooter')
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
describe('DialogTitle Component', () => {
|
|
460
|
+
it('renders correctly', () => {
|
|
461
|
+
render(<DialogTitle data-testid="title">Dialog Title</DialogTitle>)
|
|
462
|
+
|
|
463
|
+
const title = screen.getByTestId('title')
|
|
464
|
+
expect(title).toBeInTheDocument()
|
|
465
|
+
expect(title).toHaveTextContent('Dialog Title')
|
|
466
|
+
expect(title).toHaveClass('text-xl font-semibold leading-snug tracking-tight dark:text-white')
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('applies custom className', () => {
|
|
470
|
+
render(
|
|
471
|
+
<DialogTitle className="custom-title" data-testid="title">
|
|
472
|
+
Title
|
|
473
|
+
</DialogTitle>
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
const title = screen.getByTestId('title')
|
|
477
|
+
expect(title).toHaveClass('custom-title')
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('forwards ref correctly', () => {
|
|
481
|
+
const ref = React.createRef<HTMLHeadingElement>()
|
|
482
|
+
render(<DialogTitle ref={ref} data-testid="title-ref">Title</DialogTitle>)
|
|
483
|
+
|
|
484
|
+
const title = screen.getByTestId('title-ref')
|
|
485
|
+
expect(title).toBeInTheDocument()
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
it('passes through HTML attributes', () => {
|
|
489
|
+
render(
|
|
490
|
+
<DialogTitle id="title-1" data-testid="title">
|
|
491
|
+
Title
|
|
492
|
+
</DialogTitle>
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
const title = screen.getByTestId('title')
|
|
496
|
+
expect(title).toHaveAttribute('id', 'title-1')
|
|
497
|
+
})
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
describe('DialogDescription Component', () => {
|
|
501
|
+
it('renders correctly', () => {
|
|
502
|
+
render(<DialogDescription data-testid="description">Dialog Description</DialogDescription>)
|
|
503
|
+
|
|
504
|
+
const description = screen.getByTestId('description')
|
|
505
|
+
expect(description).toBeInTheDocument()
|
|
506
|
+
expect(description).toHaveTextContent('Dialog Description')
|
|
507
|
+
expect(description).toHaveClass('text-sm text-muted-foreground leading-normal dark:text-gray-400')
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
it('applies custom className', () => {
|
|
511
|
+
render(
|
|
512
|
+
<DialogDescription className="custom-description" data-testid="description">
|
|
513
|
+
Description
|
|
514
|
+
</DialogDescription>
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
const description = screen.getByTestId('description')
|
|
518
|
+
expect(description).toHaveClass('custom-description')
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
it('forwards ref correctly', () => {
|
|
522
|
+
const ref = React.createRef<HTMLParagraphElement>()
|
|
523
|
+
render(<DialogDescription ref={ref} data-testid="desc-ref">Description</DialogDescription>)
|
|
524
|
+
|
|
525
|
+
const description = screen.getByTestId('desc-ref')
|
|
526
|
+
expect(description).toBeInTheDocument()
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
it('passes through HTML attributes', () => {
|
|
530
|
+
render(
|
|
531
|
+
<DialogDescription id="desc-1" data-testid="description">
|
|
532
|
+
Description
|
|
533
|
+
</DialogDescription>
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
const description = screen.getByTestId('description')
|
|
537
|
+
expect(description).toHaveAttribute('id', 'desc-1')
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
describe('DialogClose Component', () => {
|
|
542
|
+
it('renders correctly', () => {
|
|
543
|
+
render(<DialogClose data-testid="close">Close</DialogClose>)
|
|
544
|
+
|
|
545
|
+
const close = screen.getByTestId('close')
|
|
546
|
+
expect(close).toBeInTheDocument()
|
|
547
|
+
expect(close).toHaveTextContent('Close')
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
it('handles click events', () => {
|
|
551
|
+
const onClick = jest.fn()
|
|
552
|
+
render(<DialogClose onClick={onClick}>Close</DialogClose>)
|
|
553
|
+
|
|
554
|
+
const close = screen.getByTestId('dialog-close')
|
|
555
|
+
fireEvent.click(close)
|
|
556
|
+
expect(onClick).toHaveBeenCalledTimes(1)
|
|
557
|
+
})
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
describe('DialogForm Component', () => {
|
|
561
|
+
it('renders correctly', () => {
|
|
562
|
+
render(<DialogForm data-testid="dialog-form">Form Content</DialogForm>)
|
|
563
|
+
|
|
564
|
+
const form = screen.getByTestId('dialog-form')
|
|
565
|
+
expect(form).toBeInTheDocument()
|
|
566
|
+
expect(form).toHaveTextContent('Form Content')
|
|
567
|
+
expect(form).toHaveClass('flex flex-col gap-4')
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
it('applies custom className', () => {
|
|
571
|
+
render(
|
|
572
|
+
<DialogForm className="custom-form" data-testid="dialog-form">
|
|
573
|
+
Form
|
|
574
|
+
</DialogForm>
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
const form = screen.getByTestId('dialog-form')
|
|
578
|
+
expect(form).toHaveClass('custom-form')
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
it('forwards ref correctly', () => {
|
|
582
|
+
const ref = React.createRef<HTMLFormElement>()
|
|
583
|
+
render(<DialogForm ref={ref}>Form</DialogForm>)
|
|
584
|
+
|
|
585
|
+
expect(ref.current).toBeInstanceOf(HTMLFormElement)
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
it('handles form submission', () => {
|
|
589
|
+
const onSubmit = jest.fn((e) => e.preventDefault())
|
|
590
|
+
render(
|
|
591
|
+
<DialogForm onSubmit={onSubmit} data-testid="dialog-form">
|
|
592
|
+
<button type="submit">Submit</button>
|
|
593
|
+
</DialogForm>
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
const form = screen.getByTestId('dialog-form')
|
|
597
|
+
fireEvent.submit(form)
|
|
598
|
+
expect(onSubmit).toHaveBeenCalledTimes(1)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
it('passes through HTML attributes', () => {
|
|
602
|
+
render(
|
|
603
|
+
<DialogForm id="form-1" data-testid="dialog-form">
|
|
604
|
+
Form
|
|
605
|
+
</DialogForm>
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
const form = screen.getByTestId('dialog-form')
|
|
609
|
+
expect(form).toHaveAttribute('id', 'form-1')
|
|
610
|
+
})
|
|
611
|
+
|
|
612
|
+
it('maintains displayName', () => {
|
|
613
|
+
expect(DialogForm.displayName).toBe('DialogForm')
|
|
614
|
+
})
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
describe('Complex Dialog Combinations', () => {
|
|
618
|
+
it('renders complete dialog with all components', () => {
|
|
619
|
+
render(
|
|
620
|
+
<Dialog>
|
|
621
|
+
<DialogTrigger>Open Dialog</DialogTrigger>
|
|
622
|
+
<DialogContent>
|
|
623
|
+
<DialogHeader>
|
|
624
|
+
<DialogTitle>Complete Dialog</DialogTitle>
|
|
625
|
+
<DialogDescription>This is a complete dialog example</DialogDescription>
|
|
626
|
+
</DialogHeader>
|
|
627
|
+
<div>Main content goes here</div>
|
|
628
|
+
<DialogFooter>
|
|
629
|
+
<DialogClose>Cancel</DialogClose>
|
|
630
|
+
<button>Confirm</button>
|
|
631
|
+
</DialogFooter>
|
|
632
|
+
</DialogContent>
|
|
633
|
+
</Dialog>
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
expect(screen.getByTestId('dialog-trigger')).toBeInTheDocument()
|
|
637
|
+
expect(screen.getByTestId('dialog-content')).toBeInTheDocument()
|
|
638
|
+
expect(screen.getByTestId('dialog-title')).toHaveTextContent('Complete Dialog')
|
|
639
|
+
expect(screen.getByTestId('dialog-description')).toHaveTextContent('This is a complete dialog example')
|
|
640
|
+
expect(screen.getByText('Main content goes here')).toBeInTheDocument()
|
|
641
|
+
expect(screen.getByText('Cancel')).toBeInTheDocument()
|
|
642
|
+
expect(screen.getByText('Confirm')).toBeInTheDocument()
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
it('renders dialog with form integration', () => {
|
|
646
|
+
render(
|
|
647
|
+
<Dialog>
|
|
648
|
+
<DialogContent>
|
|
649
|
+
<DialogHeader>
|
|
650
|
+
<DialogTitle>Form Dialog</DialogTitle>
|
|
651
|
+
</DialogHeader>
|
|
652
|
+
<DialogForm data-testid="dialog-form">
|
|
653
|
+
<input type="text" placeholder="Enter text" />
|
|
654
|
+
<button type="submit">Submit</button>
|
|
655
|
+
</DialogForm>
|
|
656
|
+
</DialogContent>
|
|
657
|
+
</Dialog>
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
expect(screen.getByTestId('dialog-form')).toBeInTheDocument()
|
|
661
|
+
expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument()
|
|
662
|
+
expect(screen.getByText('Submit')).toBeInTheDocument()
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
it('handles all variants and props together', () => {
|
|
666
|
+
render(
|
|
667
|
+
<Dialog open={true}>
|
|
668
|
+
<DialogContent
|
|
669
|
+
variant="primary"
|
|
670
|
+
size="lg"
|
|
671
|
+
radius="xl"
|
|
672
|
+
position="top"
|
|
673
|
+
title="Advanced Dialog"
|
|
674
|
+
description="With all features"
|
|
675
|
+
loading={false}
|
|
676
|
+
success={false}
|
|
677
|
+
hideCloseButton={false}
|
|
678
|
+
className="custom-dialog"
|
|
679
|
+
>
|
|
680
|
+
Advanced content
|
|
681
|
+
</DialogContent>
|
|
682
|
+
</Dialog>
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
const content = screen.getByTestId('dialog-content')
|
|
686
|
+
expect(content).toHaveClass('custom-dialog')
|
|
687
|
+
expect(content).toHaveClass('border-primary/20 dark:border-primary/30')
|
|
688
|
+
expect(content).toHaveClass('max-w-2xl p-7')
|
|
689
|
+
expect(content).toHaveClass('rounded-2xl')
|
|
690
|
+
expect(content).toHaveClass('top-[5%]')
|
|
691
|
+
expect(screen.getByTestId('dialog-title')).toHaveTextContent('Advanced Dialog')
|
|
692
|
+
expect(screen.getByTestId('dialog-description')).toHaveTextContent('With all features')
|
|
693
|
+
})
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
describe('Edge Cases', () => {
|
|
697
|
+
it('handles empty dialog content', () => {
|
|
698
|
+
render(
|
|
699
|
+
<Dialog>
|
|
700
|
+
<DialogContent hideCloseButton={true} data-testid="content" />
|
|
701
|
+
</Dialog>
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
const content = screen.getByTestId('content')
|
|
705
|
+
expect(content).toBeInTheDocument()
|
|
706
|
+
// Content should only contain the overlay and content wrapper
|
|
707
|
+
expect(content.children.length).toBeGreaterThanOrEqual(0)
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
it('handles null and undefined children', () => {
|
|
711
|
+
render(
|
|
712
|
+
<Dialog>
|
|
713
|
+
<DialogContent>
|
|
714
|
+
{null}
|
|
715
|
+
{undefined}
|
|
716
|
+
<div>Valid content</div>
|
|
717
|
+
</DialogContent>
|
|
718
|
+
</Dialog>
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
expect(screen.getByText('Valid content')).toBeInTheDocument()
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
it('handles dialog without trigger', () => {
|
|
725
|
+
render(
|
|
726
|
+
<Dialog>
|
|
727
|
+
<DialogContent>Content without trigger</DialogContent>
|
|
728
|
+
</Dialog>
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
expect(screen.getByTestId('dialog-content')).toBeInTheDocument()
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
it('handles multiple dialogs', () => {
|
|
735
|
+
render(
|
|
736
|
+
<div>
|
|
737
|
+
<Dialog>
|
|
738
|
+
<DialogContent data-testid="dialog-1">Dialog 1</DialogContent>
|
|
739
|
+
</Dialog>
|
|
740
|
+
<Dialog>
|
|
741
|
+
<DialogContent data-testid="dialog-2">Dialog 2</DialogContent>
|
|
742
|
+
</Dialog>
|
|
743
|
+
</div>
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
expect(screen.getByTestId('dialog-1')).toBeInTheDocument()
|
|
747
|
+
expect(screen.getByTestId('dialog-2')).toBeInTheDocument()
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
it('handles loading and success states together', () => {
|
|
751
|
+
render(
|
|
752
|
+
<Dialog>
|
|
753
|
+
<DialogContent loading={true} success={true} title="Conflicting States">
|
|
754
|
+
Content
|
|
755
|
+
</DialogContent>
|
|
756
|
+
</Dialog>
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
// Both icons should be present as they are rendered conditionally
|
|
760
|
+
expect(screen.getByTestId('loader-icon')).toBeInTheDocument()
|
|
761
|
+
expect(screen.getByTestId('check-icon')).toBeInTheDocument()
|
|
762
|
+
})
|
|
763
|
+
})
|
|
764
|
+
})
|