@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,256 @@
|
|
|
1
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import { DataTable } from '../data-table';
|
|
3
|
+
import { ColumnDef } from '@tanstack/react-table';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
|
|
6
|
+
type TestData = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
email: string;
|
|
10
|
+
age: number;
|
|
11
|
+
status: 'active' | 'inactive';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockData: TestData[] = [
|
|
15
|
+
{ id: '1', name: 'John Doe', email: 'john@example.com', age: 30, status: 'active' },
|
|
16
|
+
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', age: 25, status: 'inactive' },
|
|
17
|
+
{ id: '3', name: 'Bob Johnson', email: 'bob@example.com', age: 35, status: 'active' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const mockColumns: ColumnDef<TestData>[] = [
|
|
21
|
+
{
|
|
22
|
+
accessorKey: 'name',
|
|
23
|
+
header: 'Name',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
accessorKey: 'email',
|
|
27
|
+
header: 'Email',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
accessorKey: 'age',
|
|
31
|
+
header: 'Age',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
accessorKey: 'status',
|
|
35
|
+
header: 'Status',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
describe('DataTable', () => {
|
|
40
|
+
it('renders correctly with basic data', () => {
|
|
41
|
+
render(<DataTable columns={mockColumns} data={mockData} />);
|
|
42
|
+
|
|
43
|
+
// Check headers
|
|
44
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByText('Email')).toBeInTheDocument();
|
|
46
|
+
expect(screen.getByText('Age')).toBeInTheDocument();
|
|
47
|
+
expect(screen.getByText('Status')).toBeInTheDocument();
|
|
48
|
+
|
|
49
|
+
// Check data rows
|
|
50
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
52
|
+
expect(screen.getByText('30')).toBeInTheDocument();
|
|
53
|
+
expect(screen.getByText('active')).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('renders empty state when no data', () => {
|
|
57
|
+
render(<DataTable columns={mockColumns} data={[]} />);
|
|
58
|
+
expect(screen.getByText('No results.')).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('shows search input when searchable is true', () => {
|
|
62
|
+
render(<DataTable columns={mockColumns} data={mockData} searchable />);
|
|
63
|
+
expect(screen.getByPlaceholderText('Search all columns...')).toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('filters data based on search input', async () => {
|
|
67
|
+
render(<DataTable columns={mockColumns} data={mockData} searchable />);
|
|
68
|
+
|
|
69
|
+
const searchInput = screen.getByPlaceholderText('Search all columns...');
|
|
70
|
+
fireEvent.change(searchInput, { target: { value: 'john' } });
|
|
71
|
+
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
74
|
+
expect(screen.queryByText('Jane Smith')).not.toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('shows filter button when filterable is true', () => {
|
|
79
|
+
render(<DataTable columns={mockColumns} data={mockData} filterable />);
|
|
80
|
+
expect(screen.getByText('Filter')).toBeInTheDocument();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('shows export button when exportable is true', () => {
|
|
84
|
+
render(<DataTable columns={mockColumns} data={mockData} exportable />);
|
|
85
|
+
expect(screen.getByText('Export')).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('handles pagination correctly', () => {
|
|
89
|
+
const largeData = Array.from({ length: 25 }, (_, i) => ({
|
|
90
|
+
id: `${i + 1}`,
|
|
91
|
+
name: `User ${i + 1}`,
|
|
92
|
+
email: `user${i + 1}@example.com`,
|
|
93
|
+
age: 20 + i,
|
|
94
|
+
status: i % 2 === 0 ? 'active' as const : 'inactive' as const,
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
render(<DataTable columns={mockColumns} data={largeData} />);
|
|
98
|
+
|
|
99
|
+
// Check pagination controls
|
|
100
|
+
expect(screen.getByText('Previous')).toBeInTheDocument();
|
|
101
|
+
expect(screen.getByText('Next')).toBeInTheDocument();
|
|
102
|
+
expect(screen.getByText('1 of 3')).toBeInTheDocument(); // 25 items, 10 per page = 3 pages
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('handles sorting correctly', async () => {
|
|
106
|
+
const sortableColumns: ColumnDef<TestData>[] = [
|
|
107
|
+
{
|
|
108
|
+
accessorKey: 'name',
|
|
109
|
+
header: ({ column }) => (
|
|
110
|
+
<button onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}>
|
|
111
|
+
Name
|
|
112
|
+
</button>
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
accessorKey: 'age',
|
|
117
|
+
header: ({ column }) => (
|
|
118
|
+
<button onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}>
|
|
119
|
+
Age
|
|
120
|
+
</button>
|
|
121
|
+
),
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
render(<DataTable columns={sortableColumns} data={mockData} />);
|
|
126
|
+
|
|
127
|
+
const nameHeader = screen.getByText('Name');
|
|
128
|
+
fireEvent.click(nameHeader);
|
|
129
|
+
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
const rows = screen.getAllByRole('row');
|
|
132
|
+
// First row should be header, second should be Bob Johnson (alphabetically first)
|
|
133
|
+
expect(rows[1]).toHaveTextContent('Bob Johnson');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('handles row selection', () => {
|
|
138
|
+
const selectableColumns: ColumnDef<TestData>[] = [
|
|
139
|
+
{
|
|
140
|
+
id: 'select',
|
|
141
|
+
header: ({ table }) => (
|
|
142
|
+
<input
|
|
143
|
+
type="checkbox"
|
|
144
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
145
|
+
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
|
|
146
|
+
/>
|
|
147
|
+
),
|
|
148
|
+
cell: ({ row }) => (
|
|
149
|
+
<input
|
|
150
|
+
type="checkbox"
|
|
151
|
+
checked={row.getIsSelected()}
|
|
152
|
+
onChange={(e) => row.toggleSelected(e.target.checked)}
|
|
153
|
+
/>
|
|
154
|
+
),
|
|
155
|
+
},
|
|
156
|
+
...mockColumns,
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
render(<DataTable columns={selectableColumns} data={mockData} />);
|
|
160
|
+
|
|
161
|
+
const checkboxes = screen.getAllByRole('checkbox');
|
|
162
|
+
expect(checkboxes).toHaveLength(4); // 1 header + 3 rows
|
|
163
|
+
|
|
164
|
+
// Select first row
|
|
165
|
+
fireEvent.click(checkboxes[1]);
|
|
166
|
+
expect(checkboxes[1]).toBeChecked();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('handles column visibility', () => {
|
|
170
|
+
render(<DataTable columns={mockColumns} data={mockData} />);
|
|
171
|
+
|
|
172
|
+
// All columns should be visible by default
|
|
173
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
174
|
+
expect(screen.getByText('Email')).toBeInTheDocument();
|
|
175
|
+
expect(screen.getByText('Age')).toBeInTheDocument();
|
|
176
|
+
expect(screen.getByText('Status')).toBeInTheDocument();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('shows loading state', () => {
|
|
180
|
+
render(<DataTable columns={mockColumns} data={mockData} loading />);
|
|
181
|
+
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('handles custom cell rendering', () => {
|
|
185
|
+
const customColumns: ColumnDef<TestData>[] = [
|
|
186
|
+
{
|
|
187
|
+
accessorKey: 'name',
|
|
188
|
+
header: 'Name',
|
|
189
|
+
cell: ({ row }) => (
|
|
190
|
+
<div className="font-bold">{row.getValue('name')}</div>
|
|
191
|
+
),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
accessorKey: 'status',
|
|
195
|
+
header: 'Status',
|
|
196
|
+
cell: ({ row }) => {
|
|
197
|
+
const status = row.getValue('status') as string;
|
|
198
|
+
return (
|
|
199
|
+
<span className={status === 'active' ? 'text-green-600' : 'text-red-600'}>
|
|
200
|
+
{status}
|
|
201
|
+
</span>
|
|
202
|
+
);
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
render(<DataTable columns={customColumns} data={mockData} />);
|
|
208
|
+
|
|
209
|
+
// Check if custom rendering is applied
|
|
210
|
+
const johnDoeCell = screen.getByText('John Doe');
|
|
211
|
+
expect(johnDoeCell).toHaveClass('font-bold');
|
|
212
|
+
|
|
213
|
+
const activeStatus = screen.getAllByText('active')[0];
|
|
214
|
+
expect(activeStatus).toHaveClass('text-green-600');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('handles empty columns gracefully', () => {
|
|
218
|
+
render(<DataTable columns={[]} data={mockData} />);
|
|
219
|
+
expect(screen.getByText('No results.')).toBeInTheDocument();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('handles keyboard navigation', () => {
|
|
223
|
+
render(<DataTable columns={mockColumns} data={mockData} />);
|
|
224
|
+
|
|
225
|
+
const table = screen.getByRole('table');
|
|
226
|
+
expect(table).toBeInTheDocument();
|
|
227
|
+
|
|
228
|
+
// Table should be keyboard accessible
|
|
229
|
+
fireEvent.keyDown(table, { key: 'Tab' });
|
|
230
|
+
expect(table).toBeInTheDocument();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('exports data when export button is clicked', async () => {
|
|
234
|
+
// Mock the download functionality
|
|
235
|
+
const mockCreateObjectURL = jest.fn();
|
|
236
|
+
const mockRevokeObjectURL = jest.fn();
|
|
237
|
+
global.URL.createObjectURL = mockCreateObjectURL;
|
|
238
|
+
global.URL.revokeObjectURL = mockRevokeObjectURL;
|
|
239
|
+
|
|
240
|
+
render(<DataTable columns={mockColumns} data={mockData} exportable />);
|
|
241
|
+
|
|
242
|
+
const exportButton = screen.getByText('Export');
|
|
243
|
+
fireEvent.click(exportButton);
|
|
244
|
+
|
|
245
|
+
await waitFor(() => {
|
|
246
|
+
expect(mockCreateObjectURL).toHaveBeenCalled();
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('handles responsive design', () => {
|
|
251
|
+
render(<DataTable columns={mockColumns} data={mockData} />);
|
|
252
|
+
|
|
253
|
+
const tableContainer = screen.getByTestId('data-table-container');
|
|
254
|
+
expect(tableContainer).toHaveClass('overflow-auto');
|
|
255
|
+
});
|
|
256
|
+
});
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import { DatePicker, DateRangePicker, DateTimePicker, MonthPicker } from '../date-picker';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
|
|
5
|
+
describe('DatePicker Components', () => {
|
|
6
|
+
describe('DatePicker', () => {
|
|
7
|
+
it('renders correctly with default props', () => {
|
|
8
|
+
const mockOnChange = jest.fn();
|
|
9
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
10
|
+
|
|
11
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
12
|
+
expect(screen.getByText('Pick a date')).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('displays selected date', () => {
|
|
16
|
+
const mockOnChange = jest.fn();
|
|
17
|
+
const testDate = new Date('2024-01-15');
|
|
18
|
+
|
|
19
|
+
render(<DatePicker date={testDate} onDateChange={mockOnChange} />);
|
|
20
|
+
|
|
21
|
+
expect(screen.getByText('Jan 15, 2024')).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('opens calendar when button is clicked', async () => {
|
|
25
|
+
const mockOnChange = jest.fn();
|
|
26
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
27
|
+
|
|
28
|
+
const button = screen.getByRole('button');
|
|
29
|
+
fireEvent.click(button);
|
|
30
|
+
|
|
31
|
+
await waitFor(() => {
|
|
32
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('calls onDateChange when date is selected', async () => {
|
|
37
|
+
const mockOnChange = jest.fn();
|
|
38
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
39
|
+
|
|
40
|
+
const button = screen.getByRole('button');
|
|
41
|
+
fireEvent.click(button);
|
|
42
|
+
|
|
43
|
+
await waitFor(() => {
|
|
44
|
+
const dayButton = screen.getByText('15');
|
|
45
|
+
fireEvent.click(dayButton);
|
|
46
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('handles disabled state', () => {
|
|
51
|
+
const mockOnChange = jest.fn();
|
|
52
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} disabled />);
|
|
53
|
+
|
|
54
|
+
const button = screen.getByRole('button');
|
|
55
|
+
expect(button).toBeDisabled();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('DateRangePicker', () => {
|
|
60
|
+
it('renders correctly with default props', () => {
|
|
61
|
+
const mockOnChange = jest.fn();
|
|
62
|
+
render(<DateRangePicker dateRange={{ from: undefined, to: undefined }} onDateRangeChange={mockOnChange} />);
|
|
63
|
+
|
|
64
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByText('Pick a date range')).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('displays selected date range', () => {
|
|
69
|
+
const mockOnChange = jest.fn();
|
|
70
|
+
const dateRange = {
|
|
71
|
+
from: new Date('2024-01-15'),
|
|
72
|
+
to: new Date('2024-01-20'),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
render(<DateRangePicker dateRange={dateRange} onDateRangeChange={mockOnChange} />);
|
|
76
|
+
|
|
77
|
+
expect(screen.getByText('Jan 15, 2024 - Jan 20, 2024')).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('displays partial date range', () => {
|
|
81
|
+
const mockOnChange = jest.fn();
|
|
82
|
+
const dateRange = {
|
|
83
|
+
from: new Date('2024-01-15'),
|
|
84
|
+
to: undefined,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
render(<DateRangePicker dateRange={dateRange} onDateRangeChange={mockOnChange} />);
|
|
88
|
+
|
|
89
|
+
expect(screen.getByText('Jan 15, 2024')).toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('opens calendar when button is clicked', async () => {
|
|
93
|
+
const mockOnChange = jest.fn();
|
|
94
|
+
render(<DateRangePicker dateRange={{ from: undefined, to: undefined }} onDateRangeChange={mockOnChange} />);
|
|
95
|
+
|
|
96
|
+
const button = screen.getByRole('button');
|
|
97
|
+
fireEvent.click(button);
|
|
98
|
+
|
|
99
|
+
await waitFor(() => {
|
|
100
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('DateTimePicker', () => {
|
|
106
|
+
it('renders correctly with default props', () => {
|
|
107
|
+
const mockOnChange = jest.fn();
|
|
108
|
+
render(<DateTimePicker date={undefined} onDateChange={mockOnChange} />);
|
|
109
|
+
|
|
110
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
111
|
+
expect(screen.getByText('Pick a date and time')).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('displays selected date and time', () => {
|
|
115
|
+
const mockOnChange = jest.fn();
|
|
116
|
+
const testDate = new Date('2024-01-15T14:30:00');
|
|
117
|
+
|
|
118
|
+
render(<DateTimePicker date={testDate} onDateChange={mockOnChange} />);
|
|
119
|
+
|
|
120
|
+
expect(screen.getByText(/Jan 15, 2024.*2:30 PM/)).toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('opens calendar and time selector when button is clicked', async () => {
|
|
124
|
+
const mockOnChange = jest.fn();
|
|
125
|
+
render(<DateTimePicker date={undefined} onDateChange={mockOnChange} />);
|
|
126
|
+
|
|
127
|
+
const button = screen.getByRole('button');
|
|
128
|
+
fireEvent.click(button);
|
|
129
|
+
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
132
|
+
expect(screen.getByText('Time')).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('handles time selection', async () => {
|
|
137
|
+
const mockOnChange = jest.fn();
|
|
138
|
+
const testDate = new Date('2024-01-15T14:30:00');
|
|
139
|
+
|
|
140
|
+
render(<DateTimePicker date={testDate} onDateChange={mockOnChange} />);
|
|
141
|
+
|
|
142
|
+
const button = screen.getByRole('button');
|
|
143
|
+
fireEvent.click(button);
|
|
144
|
+
|
|
145
|
+
await waitFor(() => {
|
|
146
|
+
const hourInput = screen.getByDisplayValue('14');
|
|
147
|
+
fireEvent.change(hourInput, { target: { value: '15' } });
|
|
148
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('MonthPicker', () => {
|
|
154
|
+
it('renders correctly with default props', () => {
|
|
155
|
+
const mockOnChange = jest.fn();
|
|
156
|
+
render(<MonthPicker month={undefined} onMonthChange={mockOnChange} />);
|
|
157
|
+
|
|
158
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
159
|
+
expect(screen.getByText('Pick a month')).toBeInTheDocument();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('displays selected month', () => {
|
|
163
|
+
const mockOnChange = jest.fn();
|
|
164
|
+
const testDate = new Date('2024-01-15');
|
|
165
|
+
|
|
166
|
+
render(<MonthPicker month={testDate} onMonthChange={mockOnChange} />);
|
|
167
|
+
|
|
168
|
+
expect(screen.getByText('January 2024')).toBeInTheDocument();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('opens month selector when button is clicked', async () => {
|
|
172
|
+
const mockOnChange = jest.fn();
|
|
173
|
+
render(<MonthPicker month={undefined} onMonthChange={mockOnChange} />);
|
|
174
|
+
|
|
175
|
+
const button = screen.getByRole('button');
|
|
176
|
+
fireEvent.click(button);
|
|
177
|
+
|
|
178
|
+
await waitFor(() => {
|
|
179
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('calls onMonthChange when month is selected', async () => {
|
|
184
|
+
const mockOnChange = jest.fn();
|
|
185
|
+
render(<MonthPicker month={undefined} onMonthChange={mockOnChange} />);
|
|
186
|
+
|
|
187
|
+
const button = screen.getByRole('button');
|
|
188
|
+
fireEvent.click(button);
|
|
189
|
+
|
|
190
|
+
await waitFor(() => {
|
|
191
|
+
const monthButton = screen.getByText('January');
|
|
192
|
+
fireEvent.click(monthButton);
|
|
193
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('Common functionality', () => {
|
|
199
|
+
it('handles keyboard navigation', async () => {
|
|
200
|
+
const mockOnChange = jest.fn();
|
|
201
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
202
|
+
|
|
203
|
+
const button = screen.getByRole('button');
|
|
204
|
+
fireEvent.keyDown(button, { key: 'Enter' });
|
|
205
|
+
|
|
206
|
+
await waitFor(() => {
|
|
207
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('closes calendar when clicking outside', async () => {
|
|
212
|
+
const mockOnChange = jest.fn();
|
|
213
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
214
|
+
|
|
215
|
+
const button = screen.getByRole('button');
|
|
216
|
+
fireEvent.click(button);
|
|
217
|
+
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
fireEvent.click(document.body);
|
|
223
|
+
|
|
224
|
+
await waitFor(() => {
|
|
225
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('handles ESC key to close calendar', async () => {
|
|
230
|
+
const mockOnChange = jest.fn();
|
|
231
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
232
|
+
|
|
233
|
+
const button = screen.getByRole('button');
|
|
234
|
+
fireEvent.click(button);
|
|
235
|
+
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
fireEvent.keyDown(document, { key: 'Escape' });
|
|
241
|
+
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('applies custom className', () => {
|
|
248
|
+
const mockOnChange = jest.fn();
|
|
249
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} className="custom-class" />);
|
|
250
|
+
|
|
251
|
+
const button = screen.getByRole('button');
|
|
252
|
+
expect(button).toHaveClass('custom-class');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('Accessibility', () => {
|
|
257
|
+
it('has proper ARIA attributes', () => {
|
|
258
|
+
const mockOnChange = jest.fn();
|
|
259
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
260
|
+
|
|
261
|
+
const button = screen.getByRole('button');
|
|
262
|
+
expect(button).toHaveAttribute('aria-haspopup', 'dialog');
|
|
263
|
+
expect(button).toHaveAttribute('aria-expanded', 'false');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('updates aria-expanded when calendar is open', async () => {
|
|
267
|
+
const mockOnChange = jest.fn();
|
|
268
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
269
|
+
|
|
270
|
+
const button = screen.getByRole('button');
|
|
271
|
+
fireEvent.click(button);
|
|
272
|
+
|
|
273
|
+
await waitFor(() => {
|
|
274
|
+
expect(button).toHaveAttribute('aria-expanded', 'true');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('has proper labels for screen readers', () => {
|
|
279
|
+
const mockOnChange = jest.fn();
|
|
280
|
+
render(<DatePicker date={undefined} onDateChange={mockOnChange} />);
|
|
281
|
+
|
|
282
|
+
const button = screen.getByRole('button');
|
|
283
|
+
expect(button).toHaveAttribute('aria-label', 'Open date picker');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('Form integration', () => {
|
|
288
|
+
it('works with form validation', () => {
|
|
289
|
+
const mockOnChange = jest.fn();
|
|
290
|
+
const testDate = new Date('2024-01-15');
|
|
291
|
+
|
|
292
|
+
render(
|
|
293
|
+
<form>
|
|
294
|
+
<DatePicker date={testDate} onDateChange={mockOnChange} required />
|
|
295
|
+
</form>
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const button = screen.getByRole('button');
|
|
299
|
+
expect(button).toBeValid();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('handles form reset', () => {
|
|
303
|
+
const mockOnChange = jest.fn();
|
|
304
|
+
const testDate = new Date('2024-01-15');
|
|
305
|
+
|
|
306
|
+
render(
|
|
307
|
+
<form>
|
|
308
|
+
<DatePicker date={testDate} onDateChange={mockOnChange} />
|
|
309
|
+
<button type="reset">Reset</button>
|
|
310
|
+
</form>
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const resetButton = screen.getByText('Reset');
|
|
314
|
+
fireEvent.click(resetButton);
|
|
315
|
+
|
|
316
|
+
// Form reset should trigger appropriate behavior
|
|
317
|
+
expect(screen.getAllByRole('button')).toHaveLength(2);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
});
|