@veritree/ui 0.94.2 → 0.95.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.
@@ -0,0 +1,575 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import VTToast from '@/components/Toast/VTToast.vue';
3
+ import VTToastItem from '@/components/Toast/VTToastItem.vue';
4
+ import VTToastIcon from '@/components/Toast/VTToastIcon.vue';
5
+ import VTToastContent from '@/components/Toast/VTToastContent.vue';
6
+ import VTToastTitle from '@/components/Toast/VTToastTitle.vue';
7
+ import VTToastDescription from '@/components/Toast/VTToastDescription.vue';
8
+ import VTToastAction from '@/components/Toast/VTToastAction.vue';
9
+ import VTToastClose from '@/components/Toast/VTToastClose.vue';
10
+ import VTButton from '@/components/Button/VTButton.vue';
11
+
12
+ describe('VTToast.vue', () => {
13
+ let wrapper;
14
+
15
+ afterEach(() => {
16
+ if (wrapper) {
17
+ wrapper.destroy();
18
+ }
19
+ });
20
+
21
+ it('renders the component', () => {
22
+ wrapper = mount(VTToast);
23
+ expect(VTToast).toBeTruthy();
24
+ });
25
+
26
+ it('has the correct default props', () => {
27
+ wrapper = mount(VTToast);
28
+ expect(wrapper.props().position).toBe('top-right');
29
+ expect(wrapper.props().headless).toBe(false);
30
+ });
31
+
32
+ it('renders a dialog element', () => {
33
+ wrapper = mount(VTToast);
34
+ expect(wrapper.find('dialog').exists()).toBe(true);
35
+ });
36
+
37
+ it('displays position classes for top-right', () => {
38
+ wrapper = mount(VTToast, {
39
+ propsData: { position: 'top-right' },
40
+ });
41
+
42
+ expect(wrapper.find('dialog').classes()).toEqual(
43
+ expect.arrayContaining([
44
+ 'fixed',
45
+ 'top-6',
46
+ 'right-10',
47
+ 'bottom-auto',
48
+ 'left-auto',
49
+ 'z-50',
50
+ ]),
51
+ );
52
+ });
53
+
54
+ it('displays position classes for top-left', () => {
55
+ wrapper = mount(VTToast, {
56
+ propsData: { position: 'top-left' },
57
+ });
58
+
59
+ expect(wrapper.find('dialog').classes()).toEqual(
60
+ expect.arrayContaining([
61
+ 'fixed',
62
+ 'top-6',
63
+ 'left-10',
64
+ 'bottom-auto',
65
+ 'right-auto',
66
+ 'z-50',
67
+ ]),
68
+ );
69
+ });
70
+
71
+ it('displays position classes for bottom-right', () => {
72
+ wrapper = mount(VTToast, {
73
+ propsData: { position: 'bottom-right' },
74
+ });
75
+
76
+ expect(wrapper.find('dialog').classes()).toEqual(
77
+ expect.arrayContaining([
78
+ 'fixed',
79
+ 'bottom-6',
80
+ 'right-10',
81
+ 'top-auto',
82
+ 'left-auto',
83
+ 'z-50',
84
+ ]),
85
+ );
86
+ });
87
+
88
+ it('displays position classes for bottom-left', () => {
89
+ wrapper = mount(VTToast, {
90
+ propsData: { position: 'bottom-left' },
91
+ });
92
+
93
+ expect(wrapper.find('dialog').classes()).toEqual(
94
+ expect.arrayContaining([
95
+ 'fixed',
96
+ 'bottom-6',
97
+ 'left-10',
98
+ 'top-auto',
99
+ 'right-auto',
100
+ 'z-50',
101
+ ]),
102
+ );
103
+ });
104
+
105
+ it('displays headless class when headless prop is true', () => {
106
+ wrapper = mount(VTToast, {
107
+ propsData: { headless: true },
108
+ });
109
+
110
+ expect(wrapper.find('dialog').classes()).toContain('toast');
111
+ });
112
+
113
+ it('provides apiToast method to children', () => {
114
+ wrapper = mount(VTToast);
115
+ expect(wrapper.vm._provided.apiToast).toBeDefined();
116
+ expect(typeof wrapper.vm._provided.apiToast).toBe('function');
117
+ });
118
+ });
119
+
120
+ describe('VTToastItem.vue', () => {
121
+ let wrapper;
122
+
123
+ const createWrapper = (propsData = {}, slots = {}) => {
124
+ return mount(VTToastItem, {
125
+ propsData,
126
+ slots,
127
+ provide: {
128
+ apiToast: () => ({
129
+ componentId: 1,
130
+ registerItem: jest.fn(),
131
+ unregisterItem: jest.fn(),
132
+ position: 'top-right',
133
+ }),
134
+ },
135
+ });
136
+ };
137
+
138
+ afterEach(() => {
139
+ if (wrapper) {
140
+ wrapper.destroy();
141
+ }
142
+ });
143
+
144
+ it('renders the component', () => {
145
+ wrapper = createWrapper();
146
+ expect(VTToastItem).toBeTruthy();
147
+ });
148
+
149
+ it('has the correct default props', () => {
150
+ wrapper = createWrapper();
151
+ expect(wrapper.props().variant).toBe('default');
152
+ expect(wrapper.props().dismissable).toBe(true);
153
+ expect(wrapper.props().duration).toBe(5000);
154
+ expect(wrapper.props().headless).toBe(false);
155
+ expect(wrapper.props().as).toBe('div');
156
+ });
157
+
158
+ it('displays the component with default styles', () => {
159
+ wrapper = createWrapper();
160
+
161
+ expect(wrapper.classes()).toEqual(
162
+ expect.arrayContaining([
163
+ 'grid',
164
+ 'grid-cols-[auto_1fr_auto]',
165
+ 'items-start',
166
+ 'gap-3',
167
+ 'rounded',
168
+ 'border',
169
+ 'border-solid',
170
+ ]),
171
+ );
172
+ });
173
+
174
+ it('displays the component with success variant styles', () => {
175
+ wrapper = createWrapper({ variant: 'success' });
176
+
177
+ expect(wrapper.classes()).toEqual(
178
+ expect.arrayContaining([
179
+ 'border-success-300',
180
+ 'bg-success-50',
181
+ 'text-success-700',
182
+ ]),
183
+ );
184
+ });
185
+
186
+ it('displays the component with error variant styles', () => {
187
+ wrapper = createWrapper({ variant: 'error' });
188
+
189
+ expect(wrapper.classes()).toEqual(
190
+ expect.arrayContaining([
191
+ 'border-error-300',
192
+ 'bg-error-50',
193
+ 'text-error-700',
194
+ ]),
195
+ );
196
+ });
197
+
198
+ it('displays the component with warning variant styles', () => {
199
+ wrapper = createWrapper({ variant: 'warning' });
200
+
201
+ expect(wrapper.classes()).toEqual(
202
+ expect.arrayContaining([
203
+ 'border-warning-300',
204
+ 'bg-warning-50',
205
+ 'text-warning-700',
206
+ ]),
207
+ );
208
+ });
209
+
210
+ it('displays the component with default variant styles', () => {
211
+ wrapper = createWrapper({ variant: 'default' });
212
+
213
+ expect(wrapper.classes()).toEqual(
214
+ expect.arrayContaining(['border-gray-300', 'bg-white', 'text-gray-700']),
215
+ );
216
+ });
217
+
218
+ it('displays headless class when headless prop is true', () => {
219
+ wrapper = createWrapper({ headless: true });
220
+ expect(wrapper.classes()).toContain('toast-item');
221
+ });
222
+
223
+ it('has role="alert" and aria-live="polite"', () => {
224
+ wrapper = createWrapper();
225
+ expect(wrapper.attributes('role')).toBe('alert');
226
+ expect(wrapper.attributes('aria-live')).toBe('polite');
227
+ });
228
+
229
+ it('renders default close button when dismissable is true', () => {
230
+ wrapper = createWrapper({ dismissable: true });
231
+ expect(wrapper.findComponent(VTToastClose).exists()).toBe(true);
232
+ });
233
+
234
+ it('does not render close button when dismissable is false', () => {
235
+ wrapper = createWrapper({ dismissable: false });
236
+ expect(wrapper.findComponent(VTToastClose).exists()).toBe(false);
237
+ });
238
+
239
+ it('auto-dismisses after duration', (done) => {
240
+ wrapper = createWrapper({ duration: 100 });
241
+
242
+ expect(wrapper.vm.visible).toBe(true);
243
+
244
+ setTimeout(() => {
245
+ expect(wrapper.vm.visible).toBe(false);
246
+ expect(wrapper.emitted('close')).toBeTruthy();
247
+ done();
248
+ }, 150);
249
+ });
250
+
251
+ it('pauses timer on mouseenter', () => {
252
+ wrapper = createWrapper({ duration: 5000 });
253
+
254
+ expect(wrapper.vm.timer).not.toBeNull();
255
+
256
+ wrapper.trigger('mouseenter');
257
+
258
+ expect(wrapper.vm.timer).toBeNull();
259
+ });
260
+
261
+ it('resumes timer on mouseleave', (done) => {
262
+ wrapper = createWrapper({ duration: 100 });
263
+
264
+ wrapper.trigger('mouseenter');
265
+ expect(wrapper.vm.timer).toBeNull();
266
+
267
+ wrapper.trigger('mouseleave');
268
+ expect(wrapper.vm.timer).not.toBeNull();
269
+
270
+ setTimeout(() => {
271
+ expect(wrapper.vm.visible).toBe(false);
272
+ done();
273
+ }, 150);
274
+ });
275
+
276
+ it('provides apiToastItem method to children', () => {
277
+ wrapper = createWrapper();
278
+ expect(wrapper.vm._provided.apiToastItem).toBeDefined();
279
+ expect(typeof wrapper.vm._provided.apiToastItem).toBe('function');
280
+
281
+ const api = wrapper.vm._provided.apiToastItem();
282
+ expect(api.componentId).toBeDefined();
283
+ expect(api.variant).toBe('default');
284
+ expect(typeof api.close).toBe('function');
285
+ });
286
+ });
287
+
288
+ describe('VTToastIcon.vue', () => {
289
+ let wrapper;
290
+
291
+ const createWrapper = (propsData = {}, variant = 'default') => {
292
+ return mount(VTToastIcon, {
293
+ propsData,
294
+ provide: {
295
+ apiToastItem: () => ({
296
+ componentId: 1,
297
+ variant,
298
+ }),
299
+ },
300
+ });
301
+ };
302
+
303
+ afterEach(() => {
304
+ if (wrapper) {
305
+ wrapper.destroy();
306
+ }
307
+ });
308
+
309
+ it('renders the component', () => {
310
+ wrapper = createWrapper();
311
+ expect(VTToastIcon).toBeTruthy();
312
+ });
313
+
314
+ it('displays default icon classes', () => {
315
+ wrapper = createWrapper();
316
+ expect(wrapper.classes()).toEqual(
317
+ expect.arrayContaining(['flex', 'h-5', 'w-5', 'shrink-0']),
318
+ );
319
+ });
320
+
321
+ it('displays headless class when headless prop is true', () => {
322
+ wrapper = createWrapper({ headless: true });
323
+ expect(wrapper.classes()).toContain('toast-icon');
324
+ });
325
+ });
326
+
327
+ describe('VTToastContent.vue', () => {
328
+ let wrapper;
329
+
330
+ const createWrapper = (propsData = {}) => {
331
+ return mount(VTToastContent, {
332
+ propsData,
333
+ provide: {
334
+ apiToastItem: () => ({
335
+ componentId: 1,
336
+ }),
337
+ },
338
+ });
339
+ };
340
+
341
+ afterEach(() => {
342
+ if (wrapper) {
343
+ wrapper.destroy();
344
+ }
345
+ });
346
+
347
+ it('renders the component', () => {
348
+ wrapper = createWrapper();
349
+ expect(VTToastContent).toBeTruthy();
350
+ });
351
+
352
+ it('displays default flex column classes', () => {
353
+ wrapper = createWrapper();
354
+ expect(wrapper.classes()).toEqual(
355
+ expect.arrayContaining(['flex', 'flex-col', 'gap-1']),
356
+ );
357
+ });
358
+
359
+ it('displays headless class when headless prop is true', () => {
360
+ wrapper = createWrapper({ headless: true });
361
+ expect(wrapper.classes()).toContain('toast-content');
362
+ });
363
+ });
364
+
365
+ describe('VTToastTitle.vue', () => {
366
+ let wrapper;
367
+
368
+ const createWrapper = (propsData = {}) => {
369
+ return mount(VTToastTitle, {
370
+ propsData,
371
+ provide: {
372
+ apiToastItem: () => ({
373
+ componentId: 1,
374
+ }),
375
+ },
376
+ });
377
+ };
378
+
379
+ afterEach(() => {
380
+ if (wrapper) {
381
+ wrapper.destroy();
382
+ }
383
+ });
384
+
385
+ it('renders the component', () => {
386
+ wrapper = createWrapper();
387
+ expect(VTToastTitle).toBeTruthy();
388
+ });
389
+
390
+ it('displays default title classes', () => {
391
+ wrapper = createWrapper();
392
+ expect(wrapper.classes()).toEqual(
393
+ expect.arrayContaining(['text-sm', 'font-semibold']),
394
+ );
395
+ });
396
+
397
+ it('displays headless class when headless prop is true', () => {
398
+ wrapper = createWrapper({ headless: true });
399
+ expect(wrapper.classes()).toContain('toast-title');
400
+ });
401
+
402
+ it('has correct id attribute', () => {
403
+ wrapper = createWrapper();
404
+ expect(wrapper.attributes('id')).toBe('toast-title-1');
405
+ });
406
+ });
407
+
408
+ describe('VTToastDescription.vue', () => {
409
+ let wrapper;
410
+
411
+ const createWrapper = (propsData = {}) => {
412
+ return mount(VTToastDescription, {
413
+ propsData,
414
+ provide: {
415
+ apiToastItem: () => ({
416
+ componentId: 1,
417
+ }),
418
+ },
419
+ });
420
+ };
421
+
422
+ afterEach(() => {
423
+ if (wrapper) {
424
+ wrapper.destroy();
425
+ }
426
+ });
427
+
428
+ it('renders the component', () => {
429
+ wrapper = createWrapper();
430
+ expect(VTToastDescription).toBeTruthy();
431
+ });
432
+
433
+ it('displays default description classes', () => {
434
+ wrapper = createWrapper();
435
+ expect(wrapper.classes()).toEqual(
436
+ expect.arrayContaining(['text-sm', 'opacity-90']),
437
+ );
438
+ });
439
+
440
+ it('displays headless class when headless prop is true', () => {
441
+ wrapper = createWrapper({ headless: true });
442
+ expect(wrapper.classes()).toContain('toast-description');
443
+ });
444
+
445
+ it('has correct id attribute', () => {
446
+ wrapper = createWrapper();
447
+ expect(wrapper.attributes('id')).toBe('toast-description-1');
448
+ });
449
+ });
450
+
451
+ describe('VTToastAction.vue', () => {
452
+ let wrapper;
453
+
454
+ const createWrapper = (propsData = {}) => {
455
+ return mount(VTToastAction, {
456
+ propsData,
457
+ provide: {
458
+ apiToastItem: () => ({
459
+ componentId: 1,
460
+ }),
461
+ },
462
+ });
463
+ };
464
+
465
+ afterEach(() => {
466
+ if (wrapper) {
467
+ wrapper.destroy();
468
+ }
469
+ });
470
+
471
+ it('renders the component', () => {
472
+ wrapper = createWrapper();
473
+ expect(VTToastAction).toBeTruthy();
474
+ });
475
+
476
+ it('displays default action classes', () => {
477
+ wrapper = createWrapper();
478
+ expect(wrapper.classes()).toEqual(
479
+ expect.arrayContaining(['flex', 'flex-col', 'gap-2']),
480
+ );
481
+ });
482
+
483
+ it('displays headless class when headless prop is true', () => {
484
+ wrapper = createWrapper({ headless: true });
485
+ expect(wrapper.classes()).toContain('toast-action');
486
+ });
487
+ });
488
+
489
+ describe('VTToastClose.vue', () => {
490
+ let wrapper;
491
+ const mockClose = jest.fn();
492
+
493
+ const createWrapper = (propsData = {}) => {
494
+ return mount(VTToastClose, {
495
+ propsData,
496
+ provide: {
497
+ apiToastItem: () => ({
498
+ componentId: 1,
499
+ close: mockClose,
500
+ }),
501
+ },
502
+ stubs: {
503
+ VTButton,
504
+ },
505
+ });
506
+ };
507
+
508
+ afterEach(() => {
509
+ if (wrapper) {
510
+ wrapper.destroy();
511
+ }
512
+ mockClose.mockClear();
513
+ });
514
+
515
+ it('renders the component', () => {
516
+ wrapper = createWrapper();
517
+ expect(VTToastClose).toBeTruthy();
518
+ });
519
+
520
+ it('calls close method when clicked', async () => {
521
+ wrapper = createWrapper();
522
+
523
+ await wrapper.find('button').trigger('click');
524
+
525
+ expect(mockClose).toHaveBeenCalled();
526
+ expect(wrapper.emitted('click')).toBeTruthy();
527
+ });
528
+
529
+ it('has correct id attribute', () => {
530
+ wrapper = createWrapper();
531
+ expect(wrapper.find('button').attributes('id')).toBe('toast-close-1');
532
+ });
533
+ });
534
+
535
+ describe('Toast Component Composition', () => {
536
+ let wrapper;
537
+
538
+ afterEach(() => {
539
+ if (wrapper) {
540
+ wrapper.destroy();
541
+ }
542
+ });
543
+
544
+ it('renders complete toast with all child components', () => {
545
+ wrapper = mount(VTToast, {
546
+ slots: {
547
+ default: `
548
+ <VTToastItem variant="success">
549
+ <VTToastIcon />
550
+ <VTToastContent>
551
+ <VTToastTitle>Success!</VTToastTitle>
552
+ <VTToastDescription>Operation completed</VTToastDescription>
553
+ </VTToastContent>
554
+ <VTToastClose />
555
+ </VTToastItem>
556
+ `,
557
+ },
558
+ stubs: {
559
+ VTToastItem,
560
+ VTToastIcon,
561
+ VTToastContent,
562
+ VTToastTitle,
563
+ VTToastDescription,
564
+ VTToastClose,
565
+ },
566
+ });
567
+
568
+ expect(wrapper.findComponent(VTToastItem).exists()).toBe(true);
569
+ expect(wrapper.findComponent(VTToastIcon).exists()).toBe(true);
570
+ expect(wrapper.findComponent(VTToastContent).exists()).toBe(true);
571
+ expect(wrapper.findComponent(VTToastTitle).exists()).toBe(true);
572
+ expect(wrapper.findComponent(VTToastDescription).exists()).toBe(true);
573
+ expect(wrapper.findComponent(VTToastClose).exists()).toBe(true);
574
+ });
575
+ });