@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
|
@@ -12,23 +12,43 @@ import {
|
|
|
12
12
|
CommandItem,
|
|
13
13
|
CommandShortcut,
|
|
14
14
|
CommandSeparator,
|
|
15
|
-
commandVariants,
|
|
16
|
-
commandInputVariants,
|
|
17
|
-
commandListVariants,
|
|
18
|
-
commandGroupVariants,
|
|
19
|
-
commandItemVariants,
|
|
20
15
|
} from '../command'
|
|
21
16
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
// Dialog mock — CommandDialog kaynakta Dialog + DialogContent + DialogTitle kullanır.
|
|
18
|
+
// Üçü de mock'lanmalı; eksik biri `undefined` element type hatası verir.
|
|
19
|
+
jest.mock('../dialog', () => ({
|
|
20
|
+
Dialog: ({ children, open, onOpenChange, ...props }: any) => (
|
|
21
|
+
<div
|
|
22
|
+
data-testid="dialog"
|
|
23
|
+
data-open={open ? 'true' : 'false'}
|
|
24
|
+
data-has-open-change={onOpenChange ? 'true' : 'false'}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</div>
|
|
26
29
|
),
|
|
27
|
-
DialogContent: ({ children, className, ...props }:
|
|
28
|
-
|
|
30
|
+
DialogContent: ({ children, className, ...props }: any) => {
|
|
31
|
+
// hideCloseButton gerçek DialogContent'in prop'u; mock'taki div'e sızarsa React uyarır
|
|
32
|
+
delete props.hideCloseButton
|
|
33
|
+
return <div data-testid="dialog-content" className={className} {...props}>{children}</div>
|
|
34
|
+
},
|
|
35
|
+
DialogTitle: ({ children, className, ...props }: any) => (
|
|
36
|
+
<div data-testid="dialog-title" className={className} {...props}>{children}</div>
|
|
29
37
|
),
|
|
30
38
|
}))
|
|
31
39
|
|
|
40
|
+
// jsdom `Element.prototype.scrollIntoView` implement etmez; cmdk bir item seçildiğinde
|
|
41
|
+
// bunu çağırır. Polyfill olmadan her item render'ı TypeError ile patlar.
|
|
42
|
+
beforeAll(() => {
|
|
43
|
+
;(Element.prototype as any).scrollIntoView = jest.fn()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// cmdk alt bileşenleri (Input/List/Empty/Group/Item/Separator) Command kökündeki context
|
|
47
|
+
// store'a bağlıdır; kök olmadan render edilirlerse "Cannot read properties of undefined
|
|
48
|
+
// (reading 'subscribe')" ile patlar. Tüm alt bileşen testleri bu yardımcı ile sarılır.
|
|
49
|
+
const renderInCommand = (ui: React.ReactNode, commandProps: Record<string, unknown> = {}) =>
|
|
50
|
+
render(<Command {...commandProps}>{ui}</Command>)
|
|
51
|
+
|
|
32
52
|
describe('Command Components', () => {
|
|
33
53
|
describe('Command Component', () => {
|
|
34
54
|
it('renders correctly with default props', () => {
|
|
@@ -82,21 +102,21 @@ describe('Command Components', () => {
|
|
|
82
102
|
render(<Command size="sm" data-testid="command">Content</Command>)
|
|
83
103
|
|
|
84
104
|
const command = screen.getByTestId('command')
|
|
85
|
-
expect(command).toHaveClass('
|
|
105
|
+
expect(command).toHaveClass('min-h-[200px]')
|
|
86
106
|
})
|
|
87
107
|
|
|
88
108
|
it('renders default size correctly', () => {
|
|
89
109
|
render(<Command data-testid="command">Content</Command>)
|
|
90
110
|
|
|
91
111
|
const command = screen.getByTestId('command')
|
|
92
|
-
expect(command).toHaveClass('
|
|
112
|
+
expect(command).toHaveClass('min-h-[300px]')
|
|
93
113
|
})
|
|
94
114
|
|
|
95
115
|
it('renders lg size correctly', () => {
|
|
96
116
|
render(<Command size="lg" data-testid="command">Content</Command>)
|
|
97
117
|
|
|
98
118
|
const command = screen.getByTestId('command')
|
|
99
|
-
expect(command).toHaveClass('
|
|
119
|
+
expect(command).toHaveClass('min-h-[400px]')
|
|
100
120
|
})
|
|
101
121
|
|
|
102
122
|
it('passes through HTML attributes', () => {
|
|
@@ -119,6 +139,9 @@ describe('Command Components', () => {
|
|
|
119
139
|
const dialogContent = screen.getByTestId('dialog-content')
|
|
120
140
|
expect(dialog).toBeInTheDocument()
|
|
121
141
|
expect(dialogContent).toBeInTheDocument()
|
|
142
|
+
// Erişilebilirlik: dialog'un sr-only başlığı her zaman render edilir
|
|
143
|
+
expect(screen.getByTestId('dialog-title')).toHaveTextContent('Command Menu')
|
|
144
|
+
expect(screen.getByText('Dialog Content')).toBeInTheDocument()
|
|
122
145
|
})
|
|
123
146
|
|
|
124
147
|
it('applies custom commandClassName', () => {
|
|
@@ -142,12 +165,14 @@ describe('Command Components', () => {
|
|
|
142
165
|
|
|
143
166
|
const dialog = screen.getByTestId('dialog')
|
|
144
167
|
expect(dialog).toHaveAttribute('data-open', 'true')
|
|
168
|
+
// onOpenChange alttaki Dialog'a iletilmeli (CommandDialog kendisi tüketmemeli)
|
|
169
|
+
expect(dialog).toHaveAttribute('data-has-open-change', 'true')
|
|
145
170
|
})
|
|
146
171
|
})
|
|
147
172
|
|
|
148
173
|
describe('CommandInput Component', () => {
|
|
149
174
|
it('renders correctly with default props', () => {
|
|
150
|
-
|
|
175
|
+
renderInCommand(<CommandInput data-testid="command-input" />)
|
|
151
176
|
|
|
152
177
|
const input = screen.getByTestId('command-input')
|
|
153
178
|
expect(input).toBeInTheDocument()
|
|
@@ -157,7 +182,7 @@ describe('Command Components', () => {
|
|
|
157
182
|
})
|
|
158
183
|
|
|
159
184
|
it('renders search icon', () => {
|
|
160
|
-
|
|
185
|
+
renderInCommand(<CommandInput />)
|
|
161
186
|
|
|
162
187
|
const searchIcon = document.querySelector('svg')
|
|
163
188
|
expect(searchIcon).toBeInTheDocument()
|
|
@@ -165,7 +190,7 @@ describe('Command Components', () => {
|
|
|
165
190
|
})
|
|
166
191
|
|
|
167
192
|
it('applies custom className', () => {
|
|
168
|
-
|
|
193
|
+
renderInCommand(<CommandInput className="custom-input" data-testid="command-input" />)
|
|
169
194
|
|
|
170
195
|
const input = screen.getByTestId('command-input')
|
|
171
196
|
expect(input).toHaveClass('custom-input')
|
|
@@ -173,7 +198,7 @@ describe('Command Components', () => {
|
|
|
173
198
|
|
|
174
199
|
it('forwards ref correctly', () => {
|
|
175
200
|
const ref = React.createRef<HTMLInputElement>()
|
|
176
|
-
|
|
201
|
+
renderInCommand(<CommandInput ref={ref} />)
|
|
177
202
|
|
|
178
203
|
expect(ref.current).toBeInstanceOf(HTMLInputElement)
|
|
179
204
|
})
|
|
@@ -182,41 +207,61 @@ describe('Command Components', () => {
|
|
|
182
207
|
expect(CommandInput.displayName).toBe('CommandInput')
|
|
183
208
|
})
|
|
184
209
|
|
|
185
|
-
it('renders
|
|
186
|
-
|
|
210
|
+
it('renders inside the cmdk input wrapper', () => {
|
|
211
|
+
renderInCommand(<CommandInput data-testid="command-input" />)
|
|
187
212
|
|
|
188
213
|
const input = screen.getByTestId('command-input')
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
214
|
+
const wrapper = input.closest('[cmdk-input-wrapper]')
|
|
215
|
+
expect(wrapper).toBeInTheDocument()
|
|
216
|
+
expect(wrapper).toHaveClass('flex items-center border-b px-3')
|
|
217
|
+
// Arama ikonu wrapper içinde, input'tan önce yer alır
|
|
218
|
+
expect(wrapper?.querySelector('svg')).toBeInTheDocument()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('exposes combobox ARIA wiring', () => {
|
|
222
|
+
renderInCommand(
|
|
223
|
+
<>
|
|
224
|
+
<CommandInput data-testid="command-input" />
|
|
225
|
+
<CommandList data-testid="command-list" />
|
|
226
|
+
</>
|
|
227
|
+
)
|
|
194
228
|
|
|
195
229
|
const input = screen.getByTestId('command-input')
|
|
196
|
-
expect(input).
|
|
230
|
+
expect(input).toHaveAttribute('role', 'combobox')
|
|
231
|
+
expect(input).toHaveAttribute('aria-autocomplete', 'list')
|
|
232
|
+
expect(input).toHaveAttribute('aria-expanded', 'true')
|
|
233
|
+
// aria-controls, listenin id'sini işaret etmeli
|
|
234
|
+
expect(input).toHaveAttribute('aria-controls', screen.getByTestId('command-list').id)
|
|
197
235
|
})
|
|
198
236
|
|
|
199
237
|
it('handles input events', () => {
|
|
200
|
-
|
|
201
|
-
|
|
238
|
+
// cmdk input'u kontrollüdür ve kendi onChange'ini bağlar; tüketici arama değerini
|
|
239
|
+
// `onValueChange` ile alır.
|
|
240
|
+
const onValueChange = jest.fn()
|
|
241
|
+
renderInCommand(<CommandInput onValueChange={onValueChange} data-testid="command-input" />)
|
|
202
242
|
|
|
203
|
-
const input = screen.
|
|
243
|
+
const input = screen.getByTestId('command-input')
|
|
204
244
|
fireEvent.change(input, { target: { value: 'test' } })
|
|
205
|
-
|
|
245
|
+
|
|
246
|
+
expect(onValueChange).toHaveBeenCalledWith('test')
|
|
247
|
+
expect(input).toHaveValue('test')
|
|
206
248
|
})
|
|
207
249
|
|
|
208
250
|
it('passes through HTML attributes', () => {
|
|
209
|
-
|
|
251
|
+
// NOT: `id` cmdk tarafından label eşleşmesi için üretilir ve override edilir;
|
|
252
|
+
// diğer HTML attribute'ları olduğu gibi input'a geçer.
|
|
253
|
+
renderInCommand(<CommandInput placeholder="Search..." name="search-input" maxLength={20} />)
|
|
210
254
|
|
|
211
|
-
const input = screen.getByRole('
|
|
255
|
+
const input = screen.getByRole('combobox')
|
|
212
256
|
expect(input).toHaveAttribute('placeholder', 'Search...')
|
|
213
|
-
expect(input).toHaveAttribute('
|
|
257
|
+
expect(input).toHaveAttribute('name', 'search-input')
|
|
258
|
+
expect(input).toHaveAttribute('maxlength', '20')
|
|
214
259
|
})
|
|
215
260
|
})
|
|
216
261
|
|
|
217
262
|
describe('CommandList Component', () => {
|
|
218
263
|
it('renders correctly with default props', () => {
|
|
219
|
-
|
|
264
|
+
renderInCommand(<CommandList data-testid="command-list">List Content</CommandList>)
|
|
220
265
|
|
|
221
266
|
const list = screen.getByTestId('command-list')
|
|
222
267
|
expect(list).toBeInTheDocument()
|
|
@@ -225,7 +270,7 @@ describe('Command Components', () => {
|
|
|
225
270
|
})
|
|
226
271
|
|
|
227
272
|
it('applies custom className', () => {
|
|
228
|
-
|
|
273
|
+
renderInCommand(<CommandList className="custom-list" data-testid="command-list">Content</CommandList>)
|
|
229
274
|
|
|
230
275
|
const list = screen.getByTestId('command-list')
|
|
231
276
|
expect(list).toHaveClass('custom-list')
|
|
@@ -233,7 +278,7 @@ describe('Command Components', () => {
|
|
|
233
278
|
|
|
234
279
|
it('forwards ref correctly', () => {
|
|
235
280
|
const ref = React.createRef<HTMLDivElement>()
|
|
236
|
-
|
|
281
|
+
renderInCommand(<CommandList ref={ref}>Content</CommandList>)
|
|
237
282
|
|
|
238
283
|
expect(ref.current).toBeInstanceOf(HTMLDivElement)
|
|
239
284
|
})
|
|
@@ -242,31 +287,41 @@ describe('Command Components', () => {
|
|
|
242
287
|
expect(CommandList.displayName).toBe('CommandList')
|
|
243
288
|
})
|
|
244
289
|
|
|
245
|
-
it('
|
|
246
|
-
|
|
290
|
+
it('wraps children in the cmdk list sizer', () => {
|
|
291
|
+
renderInCommand(<CommandList data-testid="command-list">List Content</CommandList>)
|
|
247
292
|
|
|
248
293
|
const list = screen.getByTestId('command-list')
|
|
249
|
-
|
|
294
|
+
const sizer = list.querySelector('[cmdk-list-sizer]')
|
|
295
|
+
expect(sizer).toBeInTheDocument()
|
|
296
|
+
expect(sizer).toHaveTextContent('List Content')
|
|
250
297
|
})
|
|
251
298
|
|
|
252
|
-
it('
|
|
253
|
-
|
|
299
|
+
it('uses the default accessible label and honours a custom one', () => {
|
|
300
|
+
const { unmount } = renderInCommand(<CommandList data-testid="command-list">Content</CommandList>)
|
|
301
|
+
expect(screen.getByTestId('command-list')).toHaveAttribute('aria-label', 'Suggestions')
|
|
302
|
+
unmount()
|
|
254
303
|
|
|
255
|
-
|
|
256
|
-
expect(list).
|
|
304
|
+
renderInCommand(<CommandList label="Results" data-testid="command-list">Content</CommandList>)
|
|
305
|
+
expect(screen.getByTestId('command-list')).toHaveAttribute('aria-label', 'Results')
|
|
257
306
|
})
|
|
258
307
|
|
|
259
308
|
it('passes through HTML attributes', () => {
|
|
260
|
-
|
|
309
|
+
// NOT: `id` cmdk tarafından input'un aria-controls'ü için üretilir; diğer
|
|
310
|
+
// attribute'lar olduğu gibi geçer.
|
|
311
|
+
renderInCommand(<CommandList data-testid="command-list" title="Command list">Content</CommandList>)
|
|
261
312
|
|
|
262
313
|
const list = screen.getByTestId('command-list')
|
|
263
|
-
expect(list).toHaveAttribute('
|
|
314
|
+
expect(list).toHaveAttribute('title', 'Command list')
|
|
264
315
|
})
|
|
265
316
|
})
|
|
266
317
|
|
|
267
318
|
describe('CommandEmpty Component', () => {
|
|
268
319
|
it('renders correctly with default props', () => {
|
|
269
|
-
|
|
320
|
+
renderInCommand(
|
|
321
|
+
<CommandList>
|
|
322
|
+
<CommandEmpty data-testid="command-empty">No results</CommandEmpty>
|
|
323
|
+
</CommandList>
|
|
324
|
+
)
|
|
270
325
|
|
|
271
326
|
const empty = screen.getByTestId('command-empty')
|
|
272
327
|
expect(empty).toBeInTheDocument()
|
|
@@ -274,7 +329,11 @@ describe('Command Components', () => {
|
|
|
274
329
|
})
|
|
275
330
|
|
|
276
331
|
it('applies custom className', () => {
|
|
277
|
-
|
|
332
|
+
renderInCommand(
|
|
333
|
+
<CommandList>
|
|
334
|
+
<CommandEmpty className="custom-empty" data-testid="command-empty">No results</CommandEmpty>
|
|
335
|
+
</CommandList>
|
|
336
|
+
)
|
|
278
337
|
|
|
279
338
|
const empty = screen.getByTestId('command-empty')
|
|
280
339
|
expect(empty).toHaveClass('custom-empty')
|
|
@@ -282,7 +341,11 @@ describe('Command Components', () => {
|
|
|
282
341
|
|
|
283
342
|
it('forwards ref correctly', () => {
|
|
284
343
|
const ref = React.createRef<HTMLDivElement>()
|
|
285
|
-
|
|
344
|
+
renderInCommand(
|
|
345
|
+
<CommandList>
|
|
346
|
+
<CommandEmpty ref={ref}>No results</CommandEmpty>
|
|
347
|
+
</CommandList>
|
|
348
|
+
)
|
|
286
349
|
|
|
287
350
|
expect(ref.current).toBeInstanceOf(HTMLDivElement)
|
|
288
351
|
})
|
|
@@ -292,7 +355,11 @@ describe('Command Components', () => {
|
|
|
292
355
|
})
|
|
293
356
|
|
|
294
357
|
it('passes through HTML attributes', () => {
|
|
295
|
-
|
|
358
|
+
renderInCommand(
|
|
359
|
+
<CommandList>
|
|
360
|
+
<CommandEmpty data-testid="command-empty" id="empty-1">No results</CommandEmpty>
|
|
361
|
+
</CommandList>
|
|
362
|
+
)
|
|
296
363
|
|
|
297
364
|
const empty = screen.getByTestId('command-empty')
|
|
298
365
|
expect(empty).toHaveAttribute('id', 'empty-1')
|
|
@@ -301,7 +368,7 @@ describe('Command Components', () => {
|
|
|
301
368
|
|
|
302
369
|
describe('CommandGroup Component', () => {
|
|
303
370
|
it('renders correctly with default props', () => {
|
|
304
|
-
|
|
371
|
+
renderInCommand(<CommandGroup data-testid="command-group">Group Content</CommandGroup>)
|
|
305
372
|
|
|
306
373
|
const group = screen.getByTestId('command-group')
|
|
307
374
|
expect(group).toBeInTheDocument()
|
|
@@ -309,7 +376,7 @@ describe('Command Components', () => {
|
|
|
309
376
|
})
|
|
310
377
|
|
|
311
378
|
it('applies custom className', () => {
|
|
312
|
-
|
|
379
|
+
renderInCommand(<CommandGroup className="custom-group" data-testid="command-group">Content</CommandGroup>)
|
|
313
380
|
|
|
314
381
|
const group = screen.getByTestId('command-group')
|
|
315
382
|
expect(group).toHaveClass('custom-group')
|
|
@@ -317,7 +384,7 @@ describe('Command Components', () => {
|
|
|
317
384
|
|
|
318
385
|
it('forwards ref correctly', () => {
|
|
319
386
|
const ref = React.createRef<HTMLDivElement>()
|
|
320
|
-
|
|
387
|
+
renderInCommand(<CommandGroup ref={ref}>Content</CommandGroup>)
|
|
321
388
|
|
|
322
389
|
expect(ref.current).toBeInstanceOf(HTMLDivElement)
|
|
323
390
|
})
|
|
@@ -327,40 +394,55 @@ describe('Command Components', () => {
|
|
|
327
394
|
})
|
|
328
395
|
|
|
329
396
|
it('renders heading when provided', () => {
|
|
330
|
-
|
|
397
|
+
renderInCommand(
|
|
331
398
|
<CommandGroup heading="Group Title" data-testid="command-group">
|
|
332
399
|
Group Content
|
|
333
400
|
</CommandGroup>
|
|
334
401
|
)
|
|
335
402
|
|
|
336
|
-
expect(screen.getByText('Group Title')).toBeInTheDocument()
|
|
337
403
|
const heading = screen.getByText('Group Title')
|
|
338
|
-
expect(heading).
|
|
404
|
+
expect(heading).toBeInTheDocument()
|
|
405
|
+
expect(heading).toHaveAttribute('cmdk-group-heading', '')
|
|
406
|
+
// Başlık stilleri, group üzerindeki arbitrary-variant sınıflarıyla verilir
|
|
407
|
+
const group = screen.getByTestId('command-group')
|
|
408
|
+
expect(group).toHaveClass('[&_[cmdk-group-heading]]:px-2')
|
|
409
|
+
expect(group).toHaveClass('[&_[cmdk-group-heading]]:py-1.5')
|
|
410
|
+
expect(group).toHaveClass('[&_[cmdk-group-heading]]:text-xs')
|
|
411
|
+
expect(group).toHaveClass('[&_[cmdk-group-heading]]:font-medium')
|
|
412
|
+
expect(group).toHaveClass('[&_[cmdk-group-heading]]:text-muted-foreground')
|
|
339
413
|
})
|
|
340
414
|
|
|
341
415
|
it('renders without heading', () => {
|
|
342
|
-
|
|
416
|
+
renderInCommand(<CommandGroup data-testid="command-group">Group Content</CommandGroup>)
|
|
343
417
|
|
|
344
418
|
const group = screen.getByTestId('command-group')
|
|
345
|
-
expect(group.querySelector('
|
|
419
|
+
expect(group.querySelector('[cmdk-group-heading]')).not.toBeInTheDocument()
|
|
346
420
|
})
|
|
347
421
|
|
|
348
|
-
it('renders
|
|
349
|
-
|
|
422
|
+
it('renders items inside a labelled group container', () => {
|
|
423
|
+
renderInCommand(
|
|
424
|
+
<CommandGroup heading="Group Title" data-testid="command-group">
|
|
425
|
+
<CommandItem value="a">Item A</CommandItem>
|
|
426
|
+
</CommandGroup>
|
|
427
|
+
)
|
|
350
428
|
|
|
351
429
|
const group = screen.getByTestId('command-group')
|
|
352
|
-
|
|
430
|
+
const items = group.querySelector('[cmdk-group-items]')
|
|
431
|
+
expect(items).toBeInTheDocument()
|
|
432
|
+
expect(items).toHaveAttribute('role', 'group')
|
|
433
|
+
// Başlık, item konteynerine aria-labelledby ile bağlanır
|
|
434
|
+
expect(items).toHaveAttribute('aria-labelledby', screen.getByText('Group Title').id)
|
|
353
435
|
})
|
|
354
436
|
|
|
355
|
-
it('
|
|
356
|
-
|
|
437
|
+
it('marks the group as presentation for assistive tech', () => {
|
|
438
|
+
renderInCommand(<CommandGroup data-testid="command-group">Content</CommandGroup>)
|
|
357
439
|
|
|
358
440
|
const group = screen.getByTestId('command-group')
|
|
359
|
-
expect(group).
|
|
441
|
+
expect(group).toHaveAttribute('role', 'presentation')
|
|
360
442
|
})
|
|
361
443
|
|
|
362
444
|
it('passes through HTML attributes', () => {
|
|
363
|
-
|
|
445
|
+
renderInCommand(<CommandGroup data-testid="command-group" id="group-1">Content</CommandGroup>)
|
|
364
446
|
|
|
365
447
|
const group = screen.getByTestId('command-group')
|
|
366
448
|
expect(group).toHaveAttribute('id', 'group-1')
|
|
@@ -369,7 +451,7 @@ describe('Command Components', () => {
|
|
|
369
451
|
|
|
370
452
|
describe('CommandItem Component', () => {
|
|
371
453
|
it('renders correctly with default props', () => {
|
|
372
|
-
|
|
454
|
+
renderInCommand(<CommandItem data-testid="command-item">Item Content</CommandItem>)
|
|
373
455
|
|
|
374
456
|
const item = screen.getByTestId('command-item')
|
|
375
457
|
expect(item).toBeInTheDocument()
|
|
@@ -378,7 +460,7 @@ describe('Command Components', () => {
|
|
|
378
460
|
})
|
|
379
461
|
|
|
380
462
|
it('applies custom className', () => {
|
|
381
|
-
|
|
463
|
+
renderInCommand(<CommandItem className="custom-item" data-testid="command-item">Content</CommandItem>)
|
|
382
464
|
|
|
383
465
|
const item = screen.getByTestId('command-item')
|
|
384
466
|
expect(item).toHaveClass('custom-item')
|
|
@@ -386,7 +468,7 @@ describe('Command Components', () => {
|
|
|
386
468
|
|
|
387
469
|
it('forwards ref correctly', () => {
|
|
388
470
|
const ref = React.createRef<HTMLDivElement>()
|
|
389
|
-
|
|
471
|
+
renderInCommand(<CommandItem ref={ref}>Content</CommandItem>)
|
|
390
472
|
|
|
391
473
|
expect(ref.current).toBeInstanceOf(HTMLDivElement)
|
|
392
474
|
})
|
|
@@ -396,22 +478,28 @@ describe('Command Components', () => {
|
|
|
396
478
|
})
|
|
397
479
|
|
|
398
480
|
it('handles selected state', () => {
|
|
399
|
-
|
|
481
|
+
// Seçim, Command kökünün `value`'su ile item'ın value'sunun eşleşmesiyle olur.
|
|
482
|
+
renderInCommand(
|
|
483
|
+
<CommandItem value="test-value" data-testid="command-item">Content</CommandItem>,
|
|
484
|
+
{ value: 'test-value' }
|
|
485
|
+
)
|
|
400
486
|
|
|
401
487
|
const item = screen.getByTestId('command-item')
|
|
402
488
|
expect(item).toHaveAttribute('aria-selected', 'true')
|
|
489
|
+
expect(item).toHaveAttribute('data-selected', 'true')
|
|
403
490
|
})
|
|
404
491
|
|
|
405
492
|
it('handles disabled state', () => {
|
|
406
|
-
|
|
493
|
+
renderInCommand(<CommandItem disabled={true} data-testid="command-item">Content</CommandItem>)
|
|
407
494
|
|
|
408
495
|
const item = screen.getByTestId('command-item')
|
|
409
|
-
expect(item).toHaveAttribute('data-disabled', '')
|
|
496
|
+
expect(item).toHaveAttribute('data-disabled', 'true')
|
|
497
|
+
expect(item).toHaveAttribute('aria-disabled', 'true')
|
|
410
498
|
})
|
|
411
499
|
|
|
412
500
|
it('handles click events with onSelect', () => {
|
|
413
501
|
const onSelect = jest.fn()
|
|
414
|
-
|
|
502
|
+
renderInCommand(
|
|
415
503
|
<CommandItem onSelect={onSelect} value="test-value" data-testid="command-item">
|
|
416
504
|
Content
|
|
417
505
|
</CommandItem>
|
|
@@ -424,7 +512,7 @@ describe('Command Components', () => {
|
|
|
424
512
|
|
|
425
513
|
it('does not call onSelect when disabled', () => {
|
|
426
514
|
const onSelect = jest.fn()
|
|
427
|
-
|
|
515
|
+
renderInCommand(
|
|
428
516
|
<CommandItem onSelect={onSelect} disabled={true} value="test-value" data-testid="command-item">
|
|
429
517
|
Content
|
|
430
518
|
</CommandItem>
|
|
@@ -435,25 +523,46 @@ describe('Command Components', () => {
|
|
|
435
523
|
expect(onSelect).not.toHaveBeenCalled()
|
|
436
524
|
})
|
|
437
525
|
|
|
438
|
-
it('
|
|
439
|
-
|
|
526
|
+
it('filters out items that do not match the search', () => {
|
|
527
|
+
renderInCommand(
|
|
528
|
+
<>
|
|
529
|
+
<CommandInput data-testid="command-input" />
|
|
530
|
+
<CommandList>
|
|
531
|
+
<CommandItem value="apple">Apple</CommandItem>
|
|
532
|
+
<CommandItem value="banana">Banana</CommandItem>
|
|
533
|
+
</CommandList>
|
|
534
|
+
</>
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
expect(screen.getByText('Apple')).toBeInTheDocument()
|
|
538
|
+
expect(screen.getByText('Banana')).toBeInTheDocument()
|
|
440
539
|
|
|
441
|
-
|
|
442
|
-
|
|
540
|
+
fireEvent.change(screen.getByTestId('command-input'), { target: { value: 'ban' } })
|
|
541
|
+
|
|
542
|
+
expect(screen.queryByText('Apple')).not.toBeInTheDocument()
|
|
543
|
+
expect(screen.getByText('Banana')).toBeInTheDocument()
|
|
443
544
|
})
|
|
444
545
|
|
|
445
|
-
it('
|
|
446
|
-
|
|
546
|
+
it('selects the active item with the Enter key', () => {
|
|
547
|
+
const onSelect = jest.fn()
|
|
548
|
+
render(
|
|
549
|
+
<Command value="calendar" data-testid="command">
|
|
550
|
+
<CommandList>
|
|
551
|
+
<CommandItem value="calendar" onSelect={onSelect}>Calendar</CommandItem>
|
|
552
|
+
</CommandList>
|
|
553
|
+
</Command>
|
|
554
|
+
)
|
|
447
555
|
|
|
448
|
-
|
|
449
|
-
expect(
|
|
556
|
+
fireEvent.keyDown(screen.getByTestId('command'), { key: 'Enter' })
|
|
557
|
+
expect(onSelect).toHaveBeenCalledWith('calendar')
|
|
450
558
|
})
|
|
451
559
|
|
|
452
560
|
it('passes through HTML attributes', () => {
|
|
453
|
-
|
|
561
|
+
// NOT: `id` cmdk tarafından aria-activedescendant için üretilir ve override edilir.
|
|
562
|
+
renderInCommand(<CommandItem data-testid="command-item" title="Item one">Content</CommandItem>)
|
|
454
563
|
|
|
455
564
|
const item = screen.getByTestId('command-item')
|
|
456
|
-
expect(item).toHaveAttribute('
|
|
565
|
+
expect(item).toHaveAttribute('title', 'Item one')
|
|
457
566
|
})
|
|
458
567
|
})
|
|
459
568
|
|
|
@@ -487,15 +596,16 @@ describe('Command Components', () => {
|
|
|
487
596
|
|
|
488
597
|
describe('CommandSeparator Component', () => {
|
|
489
598
|
it('renders correctly with default props', () => {
|
|
490
|
-
|
|
599
|
+
renderInCommand(<CommandSeparator data-testid="command-separator" />)
|
|
491
600
|
|
|
492
601
|
const separator = screen.getByTestId('command-separator')
|
|
493
602
|
expect(separator).toBeInTheDocument()
|
|
494
603
|
expect(separator).toHaveClass('-mx-1 h-px bg-border')
|
|
604
|
+
expect(separator).toHaveAttribute('role', 'separator')
|
|
495
605
|
})
|
|
496
606
|
|
|
497
607
|
it('applies custom className', () => {
|
|
498
|
-
|
|
608
|
+
renderInCommand(<CommandSeparator className="custom-separator" data-testid="command-separator" />)
|
|
499
609
|
|
|
500
610
|
const separator = screen.getByTestId('command-separator')
|
|
501
611
|
expect(separator).toHaveClass('custom-separator')
|
|
@@ -503,7 +613,7 @@ describe('Command Components', () => {
|
|
|
503
613
|
|
|
504
614
|
it('forwards ref correctly', () => {
|
|
505
615
|
const ref = React.createRef<HTMLDivElement>()
|
|
506
|
-
|
|
616
|
+
renderInCommand(<CommandSeparator ref={ref} />)
|
|
507
617
|
|
|
508
618
|
expect(ref.current).toBeInstanceOf(HTMLDivElement)
|
|
509
619
|
})
|
|
@@ -513,42 +623,61 @@ describe('Command Components', () => {
|
|
|
513
623
|
})
|
|
514
624
|
|
|
515
625
|
it('passes through HTML attributes', () => {
|
|
516
|
-
|
|
626
|
+
renderInCommand(<CommandSeparator data-testid="command-separator" id="separator-1" />)
|
|
517
627
|
|
|
518
628
|
const separator = screen.getByTestId('command-separator')
|
|
519
629
|
expect(separator).toHaveAttribute('id', 'separator-1')
|
|
520
630
|
})
|
|
521
631
|
})
|
|
522
632
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
633
|
+
// Kaynakta yalnızca `commandVariants` (dışa aktarılmayan) CVA haritası vardır; alt
|
|
634
|
+
// bileşenlerin variant prop'u yoktur. Sınıf üretimi bu yüzden public API üzerinden,
|
|
635
|
+
// render edilen elemanların sınıfları üzerinden doğrulanır.
|
|
636
|
+
describe('Variant Classes', () => {
|
|
637
|
+
it('Command generates correct variant and size classes', () => {
|
|
638
|
+
const { unmount } = render(<Command data-testid="command">Content</Command>)
|
|
639
|
+
expect(screen.getByTestId('command')).toHaveClass('flex h-full w-full flex-col')
|
|
640
|
+
expect(screen.getByTestId('command')).toHaveClass('min-h-[300px]')
|
|
641
|
+
unmount()
|
|
642
|
+
|
|
643
|
+
const glass = render(<Command variant="glass" data-testid="command">Content</Command>)
|
|
644
|
+
expect(screen.getByTestId('command')).toHaveClass('bg-background/80')
|
|
645
|
+
glass.unmount()
|
|
646
|
+
|
|
647
|
+
render(<Command size="lg" data-testid="command">Content</Command>)
|
|
648
|
+
expect(screen.getByTestId('command')).toHaveClass('min-h-[400px]')
|
|
528
649
|
})
|
|
529
650
|
|
|
530
|
-
it('
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
expect(
|
|
651
|
+
it('CommandInput generates correct classes', () => {
|
|
652
|
+
renderInCommand(<CommandInput data-testid="command-input" />)
|
|
653
|
+
|
|
654
|
+
expect(screen.getByTestId('command-input')).toHaveClass(
|
|
655
|
+
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm'
|
|
656
|
+
)
|
|
534
657
|
})
|
|
535
658
|
|
|
536
|
-
it('
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
expect(
|
|
659
|
+
it('CommandList generates correct classes', () => {
|
|
660
|
+
renderInCommand(<CommandList data-testid="command-list">Content</CommandList>)
|
|
661
|
+
|
|
662
|
+
expect(screen.getByTestId('command-list')).toHaveClass(
|
|
663
|
+
'max-h-[300px] overflow-y-auto overflow-x-hidden'
|
|
664
|
+
)
|
|
540
665
|
})
|
|
541
666
|
|
|
542
|
-
it('
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
expect(
|
|
667
|
+
it('CommandGroup generates correct classes', () => {
|
|
668
|
+
renderInCommand(<CommandGroup data-testid="command-group">Content</CommandGroup>)
|
|
669
|
+
|
|
670
|
+
expect(screen.getByTestId('command-group')).toHaveClass('overflow-hidden p-1 text-foreground')
|
|
671
|
+
expect(screen.getByTestId('command-group')).toHaveClass('[&_[cmdk-group-heading]]:text-xs')
|
|
546
672
|
})
|
|
547
673
|
|
|
548
|
-
it('
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
expect(
|
|
674
|
+
it('CommandItem generates correct classes', () => {
|
|
675
|
+
renderInCommand(<CommandItem data-testid="command-item">Content</CommandItem>)
|
|
676
|
+
|
|
677
|
+
expect(screen.getByTestId('command-item')).toHaveClass(
|
|
678
|
+
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm'
|
|
679
|
+
)
|
|
680
|
+
expect(screen.getByTestId('command-item')).toHaveClass('aria-selected:bg-accent')
|
|
552
681
|
})
|
|
553
682
|
})
|
|
554
683
|
|
|
@@ -561,11 +690,11 @@ describe('Command Components', () => {
|
|
|
561
690
|
<CommandEmpty>No results found.</CommandEmpty>
|
|
562
691
|
<CommandGroup heading="Suggestions">
|
|
563
692
|
<CommandItem value="calendar">
|
|
564
|
-
Calendar
|
|
693
|
+
<span>Calendar</span>
|
|
565
694
|
<CommandShortcut>⌘C</CommandShortcut>
|
|
566
695
|
</CommandItem>
|
|
567
696
|
<CommandItem value="search">
|
|
568
|
-
Search
|
|
697
|
+
<span>Search</span>
|
|
569
698
|
<CommandShortcut>⌘S</CommandShortcut>
|
|
570
699
|
</CommandItem>
|
|
571
700
|
</CommandGroup>
|
|
@@ -583,6 +712,7 @@ describe('Command Components', () => {
|
|
|
583
712
|
expect(screen.getByText('Suggestions')).toBeInTheDocument()
|
|
584
713
|
expect(screen.getByText('Calendar')).toBeInTheDocument()
|
|
585
714
|
expect(screen.getByText('⌘C')).toBeInTheDocument()
|
|
715
|
+
expect(screen.getAllByRole('option')).toHaveLength(4)
|
|
586
716
|
})
|
|
587
717
|
|
|
588
718
|
it('renders command dialog with all components', () => {
|
|
@@ -602,26 +732,38 @@ describe('Command Components', () => {
|
|
|
602
732
|
expect(screen.getByTestId('dialog')).toBeInTheDocument()
|
|
603
733
|
expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument()
|
|
604
734
|
expect(screen.getByText('Actions')).toBeInTheDocument()
|
|
735
|
+
expect(screen.getByText('Action 1')).toBeInTheDocument()
|
|
605
736
|
})
|
|
606
737
|
|
|
607
738
|
it('handles all props and variants together', () => {
|
|
608
739
|
render(
|
|
609
|
-
<Command
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
740
|
+
<Command
|
|
741
|
+
variant="bordered"
|
|
742
|
+
size="lg"
|
|
743
|
+
value="test"
|
|
744
|
+
className="custom-command"
|
|
745
|
+
data-testid="command"
|
|
746
|
+
>
|
|
747
|
+
<CommandInput className="custom-input" data-testid="command-input" />
|
|
748
|
+
<CommandList className="custom-list" data-testid="command-list">
|
|
749
|
+
<CommandGroup heading="Test Group" data-testid="command-group">
|
|
750
|
+
<CommandItem value="test" data-testid="command-item">
|
|
751
|
+
<span>Test Item</span>
|
|
615
752
|
<CommandShortcut className="custom-shortcut">⌘T</CommandShortcut>
|
|
616
753
|
</CommandItem>
|
|
617
754
|
</CommandGroup>
|
|
618
|
-
<CommandSeparator className="custom-separator" />
|
|
755
|
+
<CommandSeparator className="custom-separator" data-testid="command-separator" />
|
|
619
756
|
</CommandList>
|
|
620
757
|
</Command>
|
|
621
758
|
)
|
|
622
759
|
|
|
623
|
-
|
|
624
|
-
expect(command).toHaveClass('custom-
|
|
760
|
+
expect(screen.getByTestId('command')).toHaveClass('custom-command border border-border min-h-[400px]')
|
|
761
|
+
expect(screen.getByTestId('command-input')).toHaveClass('custom-input')
|
|
762
|
+
expect(screen.getByTestId('command-list')).toHaveClass('custom-list')
|
|
763
|
+
expect(screen.getByTestId('command-separator')).toHaveClass('custom-separator')
|
|
764
|
+
expect(screen.getByText('⌘T')).toHaveClass('custom-shortcut')
|
|
765
|
+
// Command value'su item ile eşleştiği için item seçili render edilir
|
|
766
|
+
expect(screen.getByTestId('command-item')).toHaveAttribute('aria-selected', 'true')
|
|
625
767
|
})
|
|
626
768
|
})
|
|
627
769
|
|
|
@@ -634,16 +776,18 @@ describe('Command Components', () => {
|
|
|
634
776
|
})
|
|
635
777
|
|
|
636
778
|
it('handles command item without value', () => {
|
|
779
|
+
// value verilmediğinde cmdk item değerini children metninden türetir.
|
|
637
780
|
const onSelect = jest.fn()
|
|
638
|
-
|
|
781
|
+
renderInCommand(<CommandItem onSelect={onSelect} data-testid="command-item">Item</CommandItem>)
|
|
639
782
|
|
|
640
783
|
const item = screen.getByTestId('command-item')
|
|
784
|
+
expect(item).toHaveAttribute('data-value', 'Item')
|
|
641
785
|
fireEvent.click(item)
|
|
642
|
-
expect(onSelect).
|
|
786
|
+
expect(onSelect).toHaveBeenCalledWith('Item')
|
|
643
787
|
})
|
|
644
788
|
|
|
645
789
|
it('handles command group with complex heading', () => {
|
|
646
|
-
|
|
790
|
+
renderInCommand(
|
|
647
791
|
<CommandGroup heading={<span className="custom-heading">Complex Heading</span>}>
|
|
648
792
|
Content
|
|
649
793
|
</CommandGroup>
|
|
@@ -663,13 +807,13 @@ describe('Command Components', () => {
|
|
|
663
807
|
</Command>
|
|
664
808
|
)
|
|
665
809
|
|
|
666
|
-
expect(screen.getByRole('
|
|
810
|
+
expect(screen.getByRole('combobox')).toBeInTheDocument()
|
|
667
811
|
})
|
|
668
812
|
|
|
669
813
|
it('handles disabled input', () => {
|
|
670
|
-
|
|
814
|
+
renderInCommand(<CommandInput disabled placeholder="Disabled input" />)
|
|
671
815
|
|
|
672
|
-
const input = screen.getByRole('
|
|
816
|
+
const input = screen.getByRole('combobox')
|
|
673
817
|
expect(input).toBeDisabled()
|
|
674
818
|
expect(input).toHaveClass('disabled:cursor-not-allowed disabled:opacity-50')
|
|
675
819
|
})
|