@moontra/moonui 5.0.0 → 5.1.0
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/dist/hooks/index.d.mts +15 -0
- package/dist/hooks/index.d.ts +15 -0
- package/dist/hooks/index.global.js +38 -0
- package/dist/hooks/index.global.js.map +1 -0
- package/dist/hooks/index.js +128 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +106 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.global.js +9 -9
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +17 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +17 -11
- package/dist/index.mjs.map +1 -1
- package/dist/lib/theme.global.js.map +1 -1
- package/package.json +8 -3
- package/src/__tests__/hooks-exports-contract.test.ts +22 -0
- package/src/__tests__/use-intersection-observer.test.tsx +18 -4
- package/src/__tests__/use-local-storage.test.tsx +34 -20
- package/src/components/ui/__tests__/avatar.test.tsx +5 -7
- package/src/components/ui/__tests__/badge.test.tsx +19 -12
- package/src/components/ui/__tests__/breadcrumb.test.tsx +4 -7
- package/src/components/ui/__tests__/button.test.tsx +6 -3
- package/src/components/ui/__tests__/card.test.tsx +4 -1
- package/src/components/ui/__tests__/checkbox.test.tsx +4 -1
- package/src/components/ui/__tests__/command.test.tsx +268 -124
- package/src/components/ui/__tests__/data-table-basic.test.tsx +271 -0
- package/src/components/ui/__tests__/date-picker.test.tsx +184 -197
- package/src/components/ui/__tests__/progress.test.tsx +0 -1
- package/src/components/ui/__tests__/table.test.tsx +8 -2
- package/src/components/ui/__tests__/tabs.test.tsx +5 -3
- package/src/components/ui/__tests__/toast.test.tsx +461 -159
- package/src/components/ui/__tests__/tooltip.test.tsx +387 -264
- package/src/components/ui/command.tsx +11 -7
- package/src/components/ui/date-picker.tsx +12 -6
- package/src/components/ui/toast.tsx +3 -0
- package/src/hooks/index.ts +24 -0
- package/src/use-intersection-observer.tsx +10 -1
- package/src/use-local-storage.tsx +35 -1
- package/src/components/ui/__tests__/data-table.test.tsx +0 -256
- package/src/components/ui/data-table.stories.tsx +0 -511
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import { DataTableBasic } from '../data-table-basic';
|
|
3
|
+
import { ColumnDef } from '@tanstack/react-table';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* NOT (#283): Bu dosya eskiden `../data-table`'ı import ediyordu — free pakette
|
|
8
|
+
* ÖYLE BİR MODÜL YOK, bu yüzden suite hiç yüklenemiyordu (0 test koşuyordu).
|
|
9
|
+
* Free paketin dışa açtığı tablo `DataTableBasic` (bkz. ui/index.ts).
|
|
10
|
+
*
|
|
11
|
+
* Testlerin bir kısmı Pro `DataTable`'a ait özellikleri ölçüyordu ve free
|
|
12
|
+
* yüzeyde karşılıkları YOK — `filterable`, `exportable`, `loading` prop'ları ve
|
|
13
|
+
* `data-table-container` test-id'si `@moontra/moonui-pro`'daki
|
|
14
|
+
* `components/data-table/index.tsx`'te bulunuyor. O testler bu suite'ten
|
|
15
|
+
* çıkarıldı ve şu an hiçbir yerde kapsanmıyorlar: pro paketinin test altyapısı
|
|
16
|
+
* çalışır durumda (29 suite / 415 test) ama `components/data-table/` altında tek
|
|
17
|
+
* bir test dosyası yok — ayrı bir iş olarak ele alınmalı.
|
|
18
|
+
*
|
|
19
|
+
* Geri kalan her test korundu ve `DataTableBasic`'in GERÇEK yüzeyine hizalandı:
|
|
20
|
+
* enableSorting / enablePagination / enableSearch / pageSize /
|
|
21
|
+
* searchPlaceholder ("Search...") / emptyMessage ("No results found.") / className
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
type TestData = {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
email: string;
|
|
28
|
+
age: number;
|
|
29
|
+
status: 'active' | 'inactive';
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const mockData: TestData[] = [
|
|
33
|
+
{ id: '1', name: 'John Doe', email: 'john@example.com', age: 30, status: 'active' },
|
|
34
|
+
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', age: 25, status: 'inactive' },
|
|
35
|
+
{ id: '3', name: 'Bob Johnson', email: 'bob@example.com', age: 35, status: 'active' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const mockColumns: ColumnDef<TestData>[] = [
|
|
39
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
40
|
+
{ accessorKey: 'email', header: 'Email' },
|
|
41
|
+
{ accessorKey: 'age', header: 'Age' },
|
|
42
|
+
{ accessorKey: 'status', header: 'Status' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
describe('DataTableBasic', () => {
|
|
46
|
+
it('renders correctly with basic data', () => {
|
|
47
|
+
render(<DataTableBasic columns={mockColumns} data={mockData} />);
|
|
48
|
+
|
|
49
|
+
// Başlıklar
|
|
50
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByText('Email')).toBeInTheDocument();
|
|
52
|
+
expect(screen.getByText('Age')).toBeInTheDocument();
|
|
53
|
+
expect(screen.getByText('Status')).toBeInTheDocument();
|
|
54
|
+
|
|
55
|
+
// Satır verisi
|
|
56
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
57
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
58
|
+
expect(screen.getByText('30')).toBeInTheDocument();
|
|
59
|
+
// Fixture'da iki kayıt 'active' — tekil sorgu belirsiz kalıyordu
|
|
60
|
+
expect(screen.getAllByText('active')).toHaveLength(2);
|
|
61
|
+
expect(screen.getByText('inactive')).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('renders empty state when no data', () => {
|
|
65
|
+
render(<DataTableBasic columns={mockColumns} data={[]} />);
|
|
66
|
+
|
|
67
|
+
expect(screen.getByText('No results found.')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('honours a custom empty message', () => {
|
|
71
|
+
render(
|
|
72
|
+
<DataTableBasic columns={mockColumns} data={[]} emptyMessage="Nothing here yet" />
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(screen.getByText('Nothing here yet')).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('shows search input when enableSearch is true', () => {
|
|
79
|
+
render(<DataTableBasic columns={mockColumns} data={mockData} enableSearch />);
|
|
80
|
+
|
|
81
|
+
expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('hides search input when enableSearch is false', () => {
|
|
85
|
+
render(
|
|
86
|
+
<DataTableBasic columns={mockColumns} data={mockData} enableSearch={false} />
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(screen.queryByPlaceholderText('Search...')).not.toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('filters data based on search input', async () => {
|
|
93
|
+
render(<DataTableBasic columns={mockColumns} data={mockData} enableSearch />);
|
|
94
|
+
|
|
95
|
+
fireEvent.change(screen.getByPlaceholderText('Search...'), {
|
|
96
|
+
target: { value: 'Jane' },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await waitFor(() => {
|
|
100
|
+
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
|
|
101
|
+
expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
|
|
102
|
+
expect(screen.queryByText('Bob Johnson')).not.toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('handles pagination correctly', () => {
|
|
107
|
+
const largeData = Array.from({ length: 25 }, (_, i) => ({
|
|
108
|
+
id: `${i + 1}`,
|
|
109
|
+
name: `User ${i + 1}`,
|
|
110
|
+
email: `user${i + 1}@example.com`,
|
|
111
|
+
age: 20 + i,
|
|
112
|
+
status: i % 2 === 0 ? ('active' as const) : ('inactive' as const),
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
render(<DataTableBasic columns={mockColumns} data={largeData} pageSize={10} />);
|
|
116
|
+
|
|
117
|
+
expect(screen.getByText('Previous')).toBeInTheDocument();
|
|
118
|
+
expect(screen.getByText('Next')).toBeInTheDocument();
|
|
119
|
+
// Metin birden çok düğüme bölünüyor: `Page {n} of{" "}{m}`
|
|
120
|
+
expect(
|
|
121
|
+
screen.getByText((_, el) => el?.textContent?.replace(/\s+/g, ' ').trim() === 'Page 1 of 3')
|
|
122
|
+
).toBeInTheDocument();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('advances to the next page', async () => {
|
|
126
|
+
const largeData = Array.from({ length: 25 }, (_, i) => ({
|
|
127
|
+
id: `${i + 1}`,
|
|
128
|
+
name: `User ${i + 1}`,
|
|
129
|
+
email: `user${i + 1}@example.com`,
|
|
130
|
+
age: 20 + i,
|
|
131
|
+
status: 'active' as const,
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
render(<DataTableBasic columns={mockColumns} data={largeData} pageSize={10} />);
|
|
135
|
+
|
|
136
|
+
expect(screen.getByText('User 1')).toBeInTheDocument();
|
|
137
|
+
|
|
138
|
+
fireEvent.click(screen.getByText('Next'));
|
|
139
|
+
|
|
140
|
+
await waitFor(() => {
|
|
141
|
+
expect(screen.getByText('User 11')).toBeInTheDocument();
|
|
142
|
+
expect(screen.queryByText('User 1')).not.toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('handles sorting correctly', async () => {
|
|
147
|
+
const sortableColumns: ColumnDef<TestData>[] = [
|
|
148
|
+
{
|
|
149
|
+
accessorKey: 'name',
|
|
150
|
+
header: ({ column }) => (
|
|
151
|
+
<button onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}>
|
|
152
|
+
Name
|
|
153
|
+
</button>
|
|
154
|
+
),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
accessorKey: 'age',
|
|
158
|
+
header: ({ column }) => (
|
|
159
|
+
<button onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}>
|
|
160
|
+
Age
|
|
161
|
+
</button>
|
|
162
|
+
),
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
render(<DataTableBasic columns={sortableColumns} data={mockData} />);
|
|
167
|
+
|
|
168
|
+
fireEvent.click(screen.getByText('Name'));
|
|
169
|
+
|
|
170
|
+
await waitFor(() => {
|
|
171
|
+
const rows = screen.getAllByRole('row');
|
|
172
|
+
// İlk satır başlık; ikinci satır alfabetik olarak ilk kayıt olmalı
|
|
173
|
+
expect(rows[1]).toHaveTextContent('Bob Johnson');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('handles row selection', () => {
|
|
178
|
+
const selectableColumns: ColumnDef<TestData>[] = [
|
|
179
|
+
{
|
|
180
|
+
id: 'select',
|
|
181
|
+
header: ({ table }) => (
|
|
182
|
+
<input
|
|
183
|
+
type="checkbox"
|
|
184
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
185
|
+
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
|
|
186
|
+
/>
|
|
187
|
+
),
|
|
188
|
+
cell: ({ row }) => (
|
|
189
|
+
<input
|
|
190
|
+
type="checkbox"
|
|
191
|
+
checked={row.getIsSelected()}
|
|
192
|
+
onChange={(e) => row.toggleSelected(e.target.checked)}
|
|
193
|
+
/>
|
|
194
|
+
),
|
|
195
|
+
},
|
|
196
|
+
...mockColumns,
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
render(<DataTableBasic columns={selectableColumns} data={mockData} />);
|
|
200
|
+
|
|
201
|
+
const checkboxes = screen.getAllByRole('checkbox');
|
|
202
|
+
expect(checkboxes).toHaveLength(4); // 1 başlık + 3 satır
|
|
203
|
+
|
|
204
|
+
fireEvent.click(checkboxes[1]);
|
|
205
|
+
expect(checkboxes[1]).toBeChecked();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('renders every column by default', () => {
|
|
209
|
+
render(<DataTableBasic columns={mockColumns} data={mockData} />);
|
|
210
|
+
|
|
211
|
+
expect(screen.getAllByRole('columnheader')).toHaveLength(mockColumns.length);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('handles custom cell rendering', () => {
|
|
215
|
+
const customColumns: ColumnDef<TestData>[] = [
|
|
216
|
+
{
|
|
217
|
+
accessorKey: 'name',
|
|
218
|
+
header: 'Name',
|
|
219
|
+
cell: ({ row }) => <div className="font-bold">{row.getValue('name')}</div>,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
accessorKey: 'status',
|
|
223
|
+
header: 'Status',
|
|
224
|
+
cell: ({ row }) => {
|
|
225
|
+
const status = row.getValue('status') as string;
|
|
226
|
+
return (
|
|
227
|
+
<span className={status === 'active' ? 'text-green-600' : 'text-red-600'}>
|
|
228
|
+
{status}
|
|
229
|
+
</span>
|
|
230
|
+
);
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
render(<DataTableBasic columns={customColumns} data={mockData} />);
|
|
236
|
+
|
|
237
|
+
expect(screen.getByText('John Doe')).toHaveClass('font-bold');
|
|
238
|
+
expect(screen.getAllByText('active')[0]).toHaveClass('text-green-600');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('handles empty columns gracefully', () => {
|
|
242
|
+
render(<DataTableBasic columns={[]} data={mockData} />);
|
|
243
|
+
|
|
244
|
+
// Boş mesaj VERİ boşken çıkar, kolon boşken değil: veri dolu olduğu için
|
|
245
|
+
// tablo satırları kurulur, yalnızca hücre/başlık üretilmez. Buradaki
|
|
246
|
+
// gereksinim "patlamadan render etsin".
|
|
247
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
248
|
+
expect(screen.queryAllByRole('columnheader')).toHaveLength(0);
|
|
249
|
+
// Başlık satırı + veri satırları
|
|
250
|
+
expect(screen.getAllByRole('row')).toHaveLength(mockData.length + 1);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('renders a semantic, keyboard-reachable table', () => {
|
|
254
|
+
render(<DataTableBasic columns={mockColumns} data={mockData} enableSearch />);
|
|
255
|
+
|
|
256
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
257
|
+
expect(screen.getAllByRole('columnheader')).toHaveLength(mockColumns.length);
|
|
258
|
+
|
|
259
|
+
const search = screen.getByPlaceholderText('Search...');
|
|
260
|
+
search.focus();
|
|
261
|
+
expect(search).toHaveFocus();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('applies custom className', () => {
|
|
265
|
+
const { container } = render(
|
|
266
|
+
<DataTableBasic columns={mockColumns} data={mockData} className="custom-class" />
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
expect(container.querySelector('.custom-class')).toBeInTheDocument();
|
|
270
|
+
});
|
|
271
|
+
});
|