@getmicdrop/svelte-components 2.0.4 → 2.0.6

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.
Files changed (107) hide show
  1. package/dist/components/Alert/Alert.spec.js +170 -170
  2. package/dist/components/Badges/Badge.spec.js +103 -103
  3. package/dist/components/BottomSheet/BottomSheet.spec.js +127 -127
  4. package/dist/components/Breadcrumb/Breadcrumb.spec.js +120 -120
  5. package/dist/components/Button/Button.spec.js +211 -211
  6. package/dist/components/Button/ButtonSaveDemo.spec.js +48 -48
  7. package/dist/components/Calendar/Calendar.spec.js +131 -131
  8. package/dist/components/Calendar/QuarterView.spec.js +394 -394
  9. package/dist/components/Card.spec.js +47 -47
  10. package/dist/components/CropImage/CropImage.spec.js +216 -216
  11. package/dist/components/DarkModeToggle.spec.js +357 -357
  12. package/dist/components/ErrorDisplay.spec.js +69 -69
  13. package/dist/components/FormActions.spec.js +88 -88
  14. package/dist/components/FormValidationSummary.spec.js +203 -203
  15. package/dist/components/Icons/Icon.spec.js +175 -175
  16. package/dist/components/Icons/MoreHori.spec.js +67 -67
  17. package/dist/components/Icons/WarningIcon.spec.js +30 -30
  18. package/dist/components/Input/Input.spec.js +573 -573
  19. package/dist/components/Input/MultiSelect.spec.js +257 -257
  20. package/dist/components/Input/OTPInput.spec.js +238 -238
  21. package/dist/components/Input/Select.spec.js +218 -218
  22. package/dist/components/Layout/BottomNav.spec.js +130 -130
  23. package/dist/components/Layout/Header.spec.js +203 -203
  24. package/dist/components/Modal/ConfirmationModal.spec.js +191 -191
  25. package/dist/components/Modal/Modal.spec.js +95 -95
  26. package/dist/components/Modal/ModalStateManager.spec.js +100 -100
  27. package/dist/components/PageLoader.spec.js +54 -54
  28. package/dist/components/PasswordStrengthIndicator/PasswordStrengthIndicator.spec.js +173 -173
  29. package/dist/components/PlaceAutocomplete/PlaceAutocomplete.spec.js +300 -300
  30. package/dist/components/Spinner/Spinner.spec.js +75 -75
  31. package/dist/components/StatusIndicator/StatusIndicator.spec.js +129 -129
  32. package/dist/components/Toaster/Toaster.stories.svelte +1 -1
  33. package/dist/components/Toggle.spec.js +158 -158
  34. package/dist/components/ValidationError.spec.js +79 -79
  35. package/dist/components/pages/performers/AvailabilityCalendarModal.spec.js +606 -606
  36. package/dist/components/pages/performers/AvailabilityCalendarModal.svelte +4 -4
  37. package/dist/components/pages/performers/ModalShowInfo.spec.js +124 -124
  38. package/dist/components/pages/performers/ModalShowInfo.svelte +1 -1
  39. package/dist/components/pages/performers/PageBackButton.spec.js +89 -89
  40. package/dist/components/pages/performers/SectionHeader.spec.js +75 -75
  41. package/dist/components/pages/performers/ShowDetails.spec.js +166 -166
  42. package/dist/components/pages/performers/ShowItemCard.spec.js +793 -793
  43. package/dist/components/pages/performers/ShowItemCard.svelte +4 -4
  44. package/dist/components/pages/performers/SwitchOption.spec.js +127 -127
  45. package/dist/components/pages/performers/VenueInfo.spec.js +167 -167
  46. package/dist/components/pages/performers/VenueInfo.svelte +1 -1
  47. package/dist/components/pages/performers/VenueItemCard.spec.js +763 -763
  48. package/dist/components/pages/performers/VenueItemCard.svelte +4 -4
  49. package/dist/components/pages/profile/profile-form.spec.js +9 -9
  50. package/dist/components/pages/settings/tabs/CustomImageDropzone.svelte +3 -3
  51. package/dist/components/pages/shows/ShowList.spec.js +33 -33
  52. package/dist/components/pages/shows/TabContent.spec.js +90 -90
  53. package/dist/components/pages/shows/TabNavigation.spec.js +143 -143
  54. package/dist/config.js +5 -5
  55. package/dist/config.spec.js +29 -29
  56. package/dist/constants/formOptions.js +25 -25
  57. package/dist/constants/formOptions.spec.js +88 -88
  58. package/dist/index.js +111 -111
  59. package/dist/stores/auth.d.ts +9 -0
  60. package/dist/stores/auth.d.ts.map +1 -0
  61. package/dist/stores/auth.js +36 -0
  62. package/dist/stores/auth.spec.d.ts +2 -0
  63. package/dist/stores/auth.spec.d.ts.map +1 -0
  64. package/dist/stores/auth.spec.js +139 -0
  65. package/dist/stores/formDataStore.d.ts +17 -0
  66. package/dist/stores/formDataStore.d.ts.map +1 -0
  67. package/dist/stores/formDataStore.js +25 -0
  68. package/dist/stores/formDataStore.spec.d.ts +2 -0
  69. package/dist/stores/formDataStore.spec.d.ts.map +1 -0
  70. package/dist/stores/formDataStore.spec.js +257 -0
  71. package/dist/stores/formSave.d.ts +24 -0
  72. package/dist/stores/formSave.d.ts.map +1 -0
  73. package/dist/stores/formSave.js +132 -0
  74. package/dist/stores/formSave.spec.d.ts +2 -0
  75. package/dist/stores/formSave.spec.d.ts.map +1 -0
  76. package/dist/stores/formSave.spec.js +296 -0
  77. package/dist/stores/index.d.ts +1 -0
  78. package/dist/stores/index.d.ts.map +1 -0
  79. package/dist/stores/index.js +0 -0
  80. package/dist/stores/navigation.d.ts +5 -0
  81. package/dist/stores/navigation.d.ts.map +1 -0
  82. package/dist/stores/navigation.js +12 -0
  83. package/dist/stores/navigation.spec.d.ts +2 -0
  84. package/dist/stores/navigation.spec.d.ts.map +1 -0
  85. package/dist/stores/navigation.spec.js +136 -0
  86. package/dist/stores/toaster.d.ts +4 -0
  87. package/dist/stores/toaster.d.ts.map +1 -0
  88. package/dist/stores/toaster.js +13 -0
  89. package/dist/stores/toaster.spec.d.ts +2 -0
  90. package/dist/stores/toaster.spec.d.ts.map +1 -0
  91. package/dist/stores/toaster.spec.js +59 -0
  92. package/dist/telemetry.js +357 -357
  93. package/dist/telemetry.server.js +211 -211
  94. package/dist/telemetry.server.spec.js +434 -434
  95. package/dist/telemetry.spec.js +660 -660
  96. package/dist/utils/apiConfig.js +49 -49
  97. package/dist/utils/apiConfig.spec.js +118 -118
  98. package/dist/utils/greetings.js +187 -187
  99. package/dist/utils/greetings.spec.js +337 -337
  100. package/dist/utils/imageValidation.js +121 -121
  101. package/dist/utils/imageValidation.spec.js +220 -220
  102. package/dist/utils/portal.js +25 -25
  103. package/dist/utils/portal.spec.js +143 -143
  104. package/dist/utils/utils/utils.js +323 -323
  105. package/dist/utils/utils/utils.spec.js +698 -698
  106. package/dist/utils/utils.spec.js +643 -643
  107. package/package.json +1 -1
@@ -1,606 +1,606 @@
1
- import { describe, it, expect, vi, beforeEach, beforeAll, afterEach } from 'vitest';
2
- import { render, waitFor, cleanup, fireEvent } from '@testing-library/svelte';
3
-
4
- // Mock IntersectionObserver
5
- vi.stubGlobal('IntersectionObserver', vi.fn().mockImplementation(() => ({
6
- observe: vi.fn(),
7
- disconnect: vi.fn(),
8
- unobserve: vi.fn(),
9
- })));
10
-
11
- // Mock ResizeObserver
12
- vi.stubGlobal('ResizeObserver', vi.fn().mockImplementation(() => ({
13
- observe: vi.fn(),
14
- disconnect: vi.fn(),
15
- unobserve: vi.fn(),
16
- })));
17
-
18
- // Mock jwt-decode
19
- vi.mock('jwt-decode', () => ({
20
- jwtDecode: vi.fn().mockReturnValue({
21
- id: '123',
22
- userId: '123',
23
- FullName: 'Test User',
24
- }),
25
- }));
26
-
27
- // Mock $app/environment
28
- vi.mock('$app/environment', () => ({
29
- browser: true,
30
- }));
31
-
32
- // Mock stores/auth
33
- vi.mock('@/stores/auth.js', () => ({
34
- auth: {
35
- subscribe: vi.fn((callback) => {
36
- callback({ token: 'mock-token', isAuthenticated: true });
37
- return () => {};
38
- }),
39
- },
40
- initializeAuthState: vi.fn(),
41
- }));
42
-
43
- // Mock utils
44
- vi.mock('@/utils/utils/utils', () => ({
45
- timeAgo: vi.fn(() => '2 hours ago'),
46
- }));
47
-
48
- vi.mock('@/utils/apiConfig.js', () => ({
49
- buildApiUrl: vi.fn((endpoint) => `http://localhost:8080${endpoint}`),
50
- API_ENDPOINTS: {
51
- PERFORMER_AVAILABILITIES: '/api/performer/performerAvailabilities',
52
- },
53
- }));
54
-
55
- // Mock QuarterView component - needs $set method for Svelte reactivity
56
- vi.mock('@/components/Calendar/QuarterView.svelte', () => ({
57
- default: vi.fn().mockImplementation(() => ({
58
- $$: { on_mount: [], on_destroy: [], before_update: [], after_update: [] },
59
- $set: vi.fn(),
60
- $destroy: vi.fn(),
61
- })),
62
- }));
63
-
64
- // Mock fetch
65
- global.fetch = vi.fn();
66
-
67
- describe('AvailabilityCalendarModal', () => {
68
- let AvailabilityCalendarModal;
69
-
70
- const defaultVenue = {
71
- venueId: 123,
72
- name: 'Test Venue',
73
- image: '/test-image.jpg',
74
- location: '123 Main St, City, CA 90210',
75
- lastUpdated: '2025-01-01T12:00:00Z',
76
- };
77
-
78
- beforeAll(async () => {
79
- vi.resetModules();
80
-
81
- // Mock document.cookie
82
- Object.defineProperty(document, 'cookie', {
83
- writable: true,
84
- value: 'performer_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyJ9.test',
85
- });
86
-
87
- // Mock window.webkit
88
- Object.defineProperty(window, 'webkit', {
89
- value: {
90
- messageHandlers: {
91
- haptic: { postMessage: vi.fn() },
92
- },
93
- },
94
- writable: true,
95
- });
96
-
97
- // Mock navigator.vibrate
98
- Object.defineProperty(navigator, 'vibrate', {
99
- value: vi.fn(),
100
- writable: true,
101
- });
102
-
103
- AvailabilityCalendarModal = (await import('./AvailabilityCalendarModal.svelte')).default;
104
- }, 60000);
105
-
106
- beforeEach(() => {
107
- vi.clearAllMocks();
108
- global.fetch.mockReset();
109
- global.fetch.mockResolvedValue({
110
- ok: true,
111
- json: async () => [],
112
- });
113
- });
114
-
115
- afterEach(() => {
116
- cleanup();
117
- });
118
-
119
- describe('Basic Rendering', () => {
120
- it('renders when show is false', async () => {
121
- const { container } = render(AvailabilityCalendarModal, {
122
- props: {
123
- show: false,
124
- venue: defaultVenue,
125
- onClose: vi.fn(),
126
- onUpdate: vi.fn(),
127
- },
128
- });
129
- // When show is false, modal should not be visible
130
- expect(container).toBeDefined();
131
- });
132
-
133
- it('renders modal when show is true', async () => {
134
- const { container } = render(AvailabilityCalendarModal, {
135
- props: {
136
- show: true,
137
- venue: defaultVenue,
138
- onClose: vi.fn(),
139
- onUpdate: vi.fn(),
140
- },
141
- });
142
-
143
- await waitFor(() => {
144
- expect(container).toBeDefined();
145
- });
146
- });
147
-
148
- it('renders venue name in modal', async () => {
149
- const { container } = render(AvailabilityCalendarModal, {
150
- props: {
151
- show: true,
152
- venue: defaultVenue,
153
- onClose: vi.fn(),
154
- onUpdate: vi.fn(),
155
- },
156
- });
157
-
158
- await waitFor(() => {
159
- expect(container.innerHTML).toContain('Test Venue');
160
- });
161
- });
162
- });
163
-
164
- describe('Location Parsing', () => {
165
- it('parses location with 3+ parts correctly', async () => {
166
- const venue = {
167
- ...defaultVenue,
168
- location: '3400 Warner Blvd, Burbank, CA 91505',
169
- };
170
-
171
- const { container } = render(AvailabilityCalendarModal, {
172
- props: {
173
- show: true,
174
- venue,
175
- onClose: vi.fn(),
176
- onUpdate: vi.fn(),
177
- },
178
- });
179
-
180
- expect(container).toBeDefined();
181
- });
182
-
183
- it('parses location with 2 parts correctly', async () => {
184
- const venue = {
185
- ...defaultVenue,
186
- location: '123 Main St, City CA 90210',
187
- };
188
-
189
- const { container } = render(AvailabilityCalendarModal, {
190
- props: {
191
- show: true,
192
- venue,
193
- onClose: vi.fn(),
194
- onUpdate: vi.fn(),
195
- },
196
- });
197
-
198
- expect(container).toBeDefined();
199
- });
200
-
201
- it('handles single part location', async () => {
202
- const venue = {
203
- ...defaultVenue,
204
- location: '123 Main Street',
205
- };
206
-
207
- const { container } = render(AvailabilityCalendarModal, {
208
- props: {
209
- show: true,
210
- venue,
211
- onClose: vi.fn(),
212
- onUpdate: vi.fn(),
213
- },
214
- });
215
-
216
- expect(container).toBeDefined();
217
- });
218
-
219
- it('handles empty location', async () => {
220
- const venue = {
221
- ...defaultVenue,
222
- location: '',
223
- };
224
-
225
- const { container } = render(AvailabilityCalendarModal, {
226
- props: {
227
- show: true,
228
- venue,
229
- onClose: vi.fn(),
230
- onUpdate: vi.fn(),
231
- },
232
- });
233
-
234
- expect(container).toBeDefined();
235
- });
236
-
237
- it('handles null location', async () => {
238
- const venue = {
239
- ...defaultVenue,
240
- location: null,
241
- };
242
-
243
- const { container } = render(AvailabilityCalendarModal, {
244
- props: {
245
- show: true,
246
- venue,
247
- onClose: vi.fn(),
248
- onUpdate: vi.fn(),
249
- },
250
- });
251
-
252
- expect(container).toBeDefined();
253
- });
254
- });
255
-
256
- describe('Image Handling', () => {
257
- it('uses full URL for image starting with http', async () => {
258
- const venue = {
259
- ...defaultVenue,
260
- image: 'https://example.com/image.jpg',
261
- };
262
-
263
- const { container } = render(AvailabilityCalendarModal, {
264
- props: {
265
- show: true,
266
- venue,
267
- onClose: vi.fn(),
268
- onUpdate: vi.fn(),
269
- },
270
- });
271
-
272
- await waitFor(() => {
273
- const img = container.querySelector('img');
274
- expect(img?.src).toContain('https://example.com/image.jpg');
275
- });
276
- });
277
-
278
- it('prepends CDN URL for relative image paths', async () => {
279
- const venue = {
280
- ...defaultVenue,
281
- image: '/uploads/image.jpg',
282
- };
283
-
284
- const { container } = render(AvailabilityCalendarModal, {
285
- props: {
286
- show: true,
287
- venue,
288
- onClose: vi.fn(),
289
- onUpdate: vi.fn(),
290
- },
291
- });
292
-
293
- await waitFor(() => {
294
- const img = container.querySelector('img');
295
- expect(img).toBeDefined();
296
- });
297
- });
298
-
299
- it('uses placeholder for empty image', async () => {
300
- const venue = {
301
- ...defaultVenue,
302
- image: '',
303
- };
304
-
305
- const { container } = render(AvailabilityCalendarModal, {
306
- props: {
307
- show: true,
308
- venue,
309
- onClose: vi.fn(),
310
- onUpdate: vi.fn(),
311
- },
312
- });
313
-
314
- await waitFor(() => {
315
- const img = container.querySelector('img');
316
- expect(img).toBeDefined();
317
- });
318
- });
319
- });
320
-
321
- describe('Modal Interactions', () => {
322
- it('calls onClose when back button is clicked', async () => {
323
- const onClose = vi.fn();
324
-
325
- const { container } = render(AvailabilityCalendarModal, {
326
- props: {
327
- show: true,
328
- venue: defaultVenue,
329
- onClose,
330
- onUpdate: vi.fn(),
331
- },
332
- });
333
-
334
- await waitFor(() => {
335
- const backButton = container.querySelector('.modal-back-btn');
336
- if (backButton) {
337
- fireEvent.click(backButton);
338
- }
339
- });
340
-
341
- // Give time for click to be processed
342
- await new Promise(resolve => setTimeout(resolve, 100));
343
- });
344
-
345
- it('handles ESC key press', async () => {
346
- const onClose = vi.fn();
347
-
348
- render(AvailabilityCalendarModal, {
349
- props: {
350
- show: true,
351
- venue: defaultVenue,
352
- onClose,
353
- onUpdate: vi.fn(),
354
- },
355
- });
356
-
357
- await fireEvent.keyDown(window, { key: 'Escape' });
358
- });
359
- });
360
-
361
- describe('API Loading', () => {
362
- it('loads availability data when show becomes true', async () => {
363
- global.fetch.mockResolvedValueOnce({
364
- ok: true,
365
- json: async () => [
366
- { VenueID: 123, Date: '2025-03-15' },
367
- { VenueID: 123, Date: '2025-03-16' },
368
- ],
369
- });
370
-
371
- const { container } = render(AvailabilityCalendarModal, {
372
- props: {
373
- show: true,
374
- venue: defaultVenue,
375
- onClose: vi.fn(),
376
- onUpdate: vi.fn(),
377
- },
378
- });
379
-
380
- await waitFor(() => {
381
- expect(global.fetch).toHaveBeenCalled();
382
- });
383
- });
384
-
385
- it('handles API error gracefully', async () => {
386
- global.fetch.mockRejectedValueOnce(new Error('Network error'));
387
-
388
- const { container } = render(AvailabilityCalendarModal, {
389
- props: {
390
- show: true,
391
- venue: defaultVenue,
392
- onClose: vi.fn(),
393
- onUpdate: vi.fn(),
394
- },
395
- });
396
-
397
- await waitFor(() => {
398
- expect(container).toBeDefined();
399
- });
400
- });
401
-
402
- it('handles non-ok response', async () => {
403
- global.fetch.mockResolvedValueOnce({
404
- ok: false,
405
- json: async () => ({ error: 'Failed' }),
406
- });
407
-
408
- const { container } = render(AvailabilityCalendarModal, {
409
- props: {
410
- show: true,
411
- venue: defaultVenue,
412
- onClose: vi.fn(),
413
- onUpdate: vi.fn(),
414
- },
415
- });
416
-
417
- await waitFor(() => {
418
- expect(container).toBeDefined();
419
- });
420
- });
421
-
422
- it('filters events by venueId', async () => {
423
- global.fetch.mockResolvedValueOnce({
424
- ok: true,
425
- json: async () => [
426
- { VenueID: 123, Date: '2025-03-15' },
427
- { VenueID: 999, Date: '2025-03-16' }, // Different venue
428
- ],
429
- });
430
-
431
- const { container } = render(AvailabilityCalendarModal, {
432
- props: {
433
- show: true,
434
- venue: defaultVenue,
435
- onClose: vi.fn(),
436
- onUpdate: vi.fn(),
437
- },
438
- });
439
-
440
- await waitFor(() => {
441
- expect(container).toBeDefined();
442
- });
443
- });
444
- });
445
-
446
- describe('Props Handling', () => {
447
- it('handles null venue', async () => {
448
- const { container } = render(AvailabilityCalendarModal, {
449
- props: {
450
- show: true,
451
- venue: null,
452
- onClose: vi.fn(),
453
- onUpdate: vi.fn(),
454
- },
455
- });
456
-
457
- expect(container).toBeDefined();
458
- });
459
-
460
- it('handles undefined venue', async () => {
461
- const { container } = render(AvailabilityCalendarModal, {
462
- props: {
463
- show: true,
464
- venue: undefined,
465
- onClose: vi.fn(),
466
- onUpdate: vi.fn(),
467
- },
468
- });
469
-
470
- expect(container).toBeDefined();
471
- });
472
-
473
- it('handles venueId 0', async () => {
474
- const venue = {
475
- ...defaultVenue,
476
- venueId: 0,
477
- };
478
-
479
- const { container } = render(AvailabilityCalendarModal, {
480
- props: {
481
- show: true,
482
- venue,
483
- onClose: vi.fn(),
484
- onUpdate: vi.fn(),
485
- },
486
- });
487
-
488
- expect(container).toBeDefined();
489
- });
490
- });
491
-
492
- describe('Loading State', () => {
493
- it('shows loading skeleton while fetching', async () => {
494
- // Delay the fetch response
495
- global.fetch.mockImplementationOnce(() =>
496
- new Promise((resolve) => setTimeout(() => resolve({
497
- ok: true,
498
- json: async () => [],
499
- }), 500))
500
- );
501
-
502
- const { container } = render(AvailabilityCalendarModal, {
503
- props: {
504
- show: true,
505
- venue: defaultVenue,
506
- onClose: vi.fn(),
507
- onUpdate: vi.fn(),
508
- },
509
- });
510
-
511
- // Check for loading skeleton
512
- await waitFor(() => {
513
- const skeleton = container.querySelector('.loading-skeleton');
514
- expect(skeleton || container).toBeDefined();
515
- });
516
- });
517
- });
518
-
519
- describe('Save Status Messages', () => {
520
- it('initializes with empty message', async () => {
521
- const { container } = render(AvailabilityCalendarModal, {
522
- props: {
523
- show: true,
524
- venue: defaultVenue,
525
- onClose: vi.fn(),
526
- onUpdate: vi.fn(),
527
- },
528
- });
529
-
530
- expect(container).toBeDefined();
531
- });
532
- });
533
-
534
- describe('Cookie Handling', () => {
535
- it('handles missing token cookie', async () => {
536
- const originalCookie = document.cookie;
537
- Object.defineProperty(document, 'cookie', {
538
- writable: true,
539
- value: '',
540
- });
541
-
542
- const { container } = render(AvailabilityCalendarModal, {
543
- props: {
544
- show: true,
545
- venue: defaultVenue,
546
- onClose: vi.fn(),
547
- onUpdate: vi.fn(),
548
- },
549
- });
550
-
551
- expect(container).toBeDefined();
552
-
553
- // Restore
554
- Object.defineProperty(document, 'cookie', {
555
- writable: true,
556
- value: originalCookie,
557
- });
558
- });
559
- });
560
-
561
- describe('Modal Variants', () => {
562
- it('renders mobile modal variant', async () => {
563
- const { container } = render(AvailabilityCalendarModal, {
564
- props: {
565
- show: true,
566
- venue: defaultVenue,
567
- onClose: vi.fn(),
568
- onUpdate: vi.fn(),
569
- },
570
- });
571
-
572
- await waitFor(() => {
573
- const mobileModal = container.querySelector('.fullscreen-modal');
574
- expect(mobileModal).toBeDefined();
575
- });
576
- });
577
-
578
- it('renders desktop side panel', async () => {
579
- const { container } = render(AvailabilityCalendarModal, {
580
- props: {
581
- show: true,
582
- venue: defaultVenue,
583
- onClose: vi.fn(),
584
- onUpdate: vi.fn(),
585
- },
586
- });
587
-
588
- expect(container).toBeDefined();
589
- });
590
- });
591
-
592
- describe('Transitions', () => {
593
- it('applies fade transition to modal', async () => {
594
- const { container } = render(AvailabilityCalendarModal, {
595
- props: {
596
- show: true,
597
- venue: defaultVenue,
598
- onClose: vi.fn(),
599
- onUpdate: vi.fn(),
600
- },
601
- });
602
-
603
- expect(container).toBeDefined();
604
- });
605
- });
606
- });
1
+ import { describe, it, expect, vi, beforeEach, beforeAll, afterEach } from 'vitest';
2
+ import { render, waitFor, cleanup, fireEvent } from '@testing-library/svelte';
3
+
4
+ // Mock IntersectionObserver
5
+ vi.stubGlobal('IntersectionObserver', vi.fn().mockImplementation(() => ({
6
+ observe: vi.fn(),
7
+ disconnect: vi.fn(),
8
+ unobserve: vi.fn(),
9
+ })));
10
+
11
+ // Mock ResizeObserver
12
+ vi.stubGlobal('ResizeObserver', vi.fn().mockImplementation(() => ({
13
+ observe: vi.fn(),
14
+ disconnect: vi.fn(),
15
+ unobserve: vi.fn(),
16
+ })));
17
+
18
+ // Mock jwt-decode
19
+ vi.mock('jwt-decode', () => ({
20
+ jwtDecode: vi.fn().mockReturnValue({
21
+ id: '123',
22
+ userId: '123',
23
+ FullName: 'Test User',
24
+ }),
25
+ }));
26
+
27
+ // Mock $app/environment
28
+ vi.mock('$app/environment', () => ({
29
+ browser: true,
30
+ }));
31
+
32
+ // Mock stores/auth
33
+ vi.mock('@/stores/auth.js', () => ({
34
+ auth: {
35
+ subscribe: vi.fn((callback) => {
36
+ callback({ token: 'mock-token', isAuthenticated: true });
37
+ return () => {};
38
+ }),
39
+ },
40
+ initializeAuthState: vi.fn(),
41
+ }));
42
+
43
+ // Mock utils
44
+ vi.mock('@/utils/utils/utils', () => ({
45
+ timeAgo: vi.fn(() => '2 hours ago'),
46
+ }));
47
+
48
+ vi.mock('@/utils/apiConfig.js', () => ({
49
+ buildApiUrl: vi.fn((endpoint) => `http://localhost:8080${endpoint}`),
50
+ API_ENDPOINTS: {
51
+ PERFORMER_AVAILABILITIES: '/api/performer/performerAvailabilities',
52
+ },
53
+ }));
54
+
55
+ // Mock QuarterView component - needs $set method for Svelte reactivity
56
+ vi.mock('@/components/Calendar/QuarterView.svelte', () => ({
57
+ default: vi.fn().mockImplementation(() => ({
58
+ $$: { on_mount: [], on_destroy: [], before_update: [], after_update: [] },
59
+ $set: vi.fn(),
60
+ $destroy: vi.fn(),
61
+ })),
62
+ }));
63
+
64
+ // Mock fetch
65
+ global.fetch = vi.fn();
66
+
67
+ describe('AvailabilityCalendarModal', () => {
68
+ let AvailabilityCalendarModal;
69
+
70
+ const defaultVenue = {
71
+ venueId: 123,
72
+ name: 'Test Venue',
73
+ image: '/test-image.jpg',
74
+ location: '123 Main St, City, CA 90210',
75
+ lastUpdated: '2025-01-01T12:00:00Z',
76
+ };
77
+
78
+ beforeAll(async () => {
79
+ vi.resetModules();
80
+
81
+ // Mock document.cookie
82
+ Object.defineProperty(document, 'cookie', {
83
+ writable: true,
84
+ value: 'performer_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyJ9.test',
85
+ });
86
+
87
+ // Mock window.webkit
88
+ Object.defineProperty(window, 'webkit', {
89
+ value: {
90
+ messageHandlers: {
91
+ haptic: { postMessage: vi.fn() },
92
+ },
93
+ },
94
+ writable: true,
95
+ });
96
+
97
+ // Mock navigator.vibrate
98
+ Object.defineProperty(navigator, 'vibrate', {
99
+ value: vi.fn(),
100
+ writable: true,
101
+ });
102
+
103
+ AvailabilityCalendarModal = (await import('./AvailabilityCalendarModal.svelte')).default;
104
+ }, 60000);
105
+
106
+ beforeEach(() => {
107
+ vi.clearAllMocks();
108
+ global.fetch.mockReset();
109
+ global.fetch.mockResolvedValue({
110
+ ok: true,
111
+ json: async () => [],
112
+ });
113
+ });
114
+
115
+ afterEach(() => {
116
+ cleanup();
117
+ });
118
+
119
+ describe('Basic Rendering', () => {
120
+ it('renders when show is false', async () => {
121
+ const { container } = render(AvailabilityCalendarModal, {
122
+ props: {
123
+ show: false,
124
+ venue: defaultVenue,
125
+ onClose: vi.fn(),
126
+ onUpdate: vi.fn(),
127
+ },
128
+ });
129
+ // When show is false, modal should not be visible
130
+ expect(container).toBeDefined();
131
+ });
132
+
133
+ it('renders modal when show is true', async () => {
134
+ const { container } = render(AvailabilityCalendarModal, {
135
+ props: {
136
+ show: true,
137
+ venue: defaultVenue,
138
+ onClose: vi.fn(),
139
+ onUpdate: vi.fn(),
140
+ },
141
+ });
142
+
143
+ await waitFor(() => {
144
+ expect(container).toBeDefined();
145
+ });
146
+ });
147
+
148
+ it('renders venue name in modal', async () => {
149
+ const { container } = render(AvailabilityCalendarModal, {
150
+ props: {
151
+ show: true,
152
+ venue: defaultVenue,
153
+ onClose: vi.fn(),
154
+ onUpdate: vi.fn(),
155
+ },
156
+ });
157
+
158
+ await waitFor(() => {
159
+ expect(container.innerHTML).toContain('Test Venue');
160
+ });
161
+ });
162
+ });
163
+
164
+ describe('Location Parsing', () => {
165
+ it('parses location with 3+ parts correctly', async () => {
166
+ const venue = {
167
+ ...defaultVenue,
168
+ location: '3400 Warner Blvd, Burbank, CA 91505',
169
+ };
170
+
171
+ const { container } = render(AvailabilityCalendarModal, {
172
+ props: {
173
+ show: true,
174
+ venue,
175
+ onClose: vi.fn(),
176
+ onUpdate: vi.fn(),
177
+ },
178
+ });
179
+
180
+ expect(container).toBeDefined();
181
+ });
182
+
183
+ it('parses location with 2 parts correctly', async () => {
184
+ const venue = {
185
+ ...defaultVenue,
186
+ location: '123 Main St, City CA 90210',
187
+ };
188
+
189
+ const { container } = render(AvailabilityCalendarModal, {
190
+ props: {
191
+ show: true,
192
+ venue,
193
+ onClose: vi.fn(),
194
+ onUpdate: vi.fn(),
195
+ },
196
+ });
197
+
198
+ expect(container).toBeDefined();
199
+ });
200
+
201
+ it('handles single part location', async () => {
202
+ const venue = {
203
+ ...defaultVenue,
204
+ location: '123 Main Street',
205
+ };
206
+
207
+ const { container } = render(AvailabilityCalendarModal, {
208
+ props: {
209
+ show: true,
210
+ venue,
211
+ onClose: vi.fn(),
212
+ onUpdate: vi.fn(),
213
+ },
214
+ });
215
+
216
+ expect(container).toBeDefined();
217
+ });
218
+
219
+ it('handles empty location', async () => {
220
+ const venue = {
221
+ ...defaultVenue,
222
+ location: '',
223
+ };
224
+
225
+ const { container } = render(AvailabilityCalendarModal, {
226
+ props: {
227
+ show: true,
228
+ venue,
229
+ onClose: vi.fn(),
230
+ onUpdate: vi.fn(),
231
+ },
232
+ });
233
+
234
+ expect(container).toBeDefined();
235
+ });
236
+
237
+ it('handles null location', async () => {
238
+ const venue = {
239
+ ...defaultVenue,
240
+ location: null,
241
+ };
242
+
243
+ const { container } = render(AvailabilityCalendarModal, {
244
+ props: {
245
+ show: true,
246
+ venue,
247
+ onClose: vi.fn(),
248
+ onUpdate: vi.fn(),
249
+ },
250
+ });
251
+
252
+ expect(container).toBeDefined();
253
+ });
254
+ });
255
+
256
+ describe('Image Handling', () => {
257
+ it('uses full URL for image starting with http', async () => {
258
+ const venue = {
259
+ ...defaultVenue,
260
+ image: 'https://example.com/image.jpg',
261
+ };
262
+
263
+ const { container } = render(AvailabilityCalendarModal, {
264
+ props: {
265
+ show: true,
266
+ venue,
267
+ onClose: vi.fn(),
268
+ onUpdate: vi.fn(),
269
+ },
270
+ });
271
+
272
+ await waitFor(() => {
273
+ const img = container.querySelector('img');
274
+ expect(img?.src).toContain('https://example.com/image.jpg');
275
+ });
276
+ });
277
+
278
+ it('prepends CDN URL for relative image paths', async () => {
279
+ const venue = {
280
+ ...defaultVenue,
281
+ image: '/uploads/image.jpg',
282
+ };
283
+
284
+ const { container } = render(AvailabilityCalendarModal, {
285
+ props: {
286
+ show: true,
287
+ venue,
288
+ onClose: vi.fn(),
289
+ onUpdate: vi.fn(),
290
+ },
291
+ });
292
+
293
+ await waitFor(() => {
294
+ const img = container.querySelector('img');
295
+ expect(img).toBeDefined();
296
+ });
297
+ });
298
+
299
+ it('uses placeholder for empty image', async () => {
300
+ const venue = {
301
+ ...defaultVenue,
302
+ image: '',
303
+ };
304
+
305
+ const { container } = render(AvailabilityCalendarModal, {
306
+ props: {
307
+ show: true,
308
+ venue,
309
+ onClose: vi.fn(),
310
+ onUpdate: vi.fn(),
311
+ },
312
+ });
313
+
314
+ await waitFor(() => {
315
+ const img = container.querySelector('img');
316
+ expect(img).toBeDefined();
317
+ });
318
+ });
319
+ });
320
+
321
+ describe('Modal Interactions', () => {
322
+ it('calls onClose when back button is clicked', async () => {
323
+ const onClose = vi.fn();
324
+
325
+ const { container } = render(AvailabilityCalendarModal, {
326
+ props: {
327
+ show: true,
328
+ venue: defaultVenue,
329
+ onClose,
330
+ onUpdate: vi.fn(),
331
+ },
332
+ });
333
+
334
+ await waitFor(() => {
335
+ const backButton = container.querySelector('.modal-back-btn');
336
+ if (backButton) {
337
+ fireEvent.click(backButton);
338
+ }
339
+ });
340
+
341
+ // Give time for click to be processed
342
+ await new Promise(resolve => setTimeout(resolve, 100));
343
+ });
344
+
345
+ it('handles ESC key press', async () => {
346
+ const onClose = vi.fn();
347
+
348
+ render(AvailabilityCalendarModal, {
349
+ props: {
350
+ show: true,
351
+ venue: defaultVenue,
352
+ onClose,
353
+ onUpdate: vi.fn(),
354
+ },
355
+ });
356
+
357
+ await fireEvent.keyDown(window, { key: 'Escape' });
358
+ });
359
+ });
360
+
361
+ describe('API Loading', () => {
362
+ it('loads availability data when show becomes true', async () => {
363
+ global.fetch.mockResolvedValueOnce({
364
+ ok: true,
365
+ json: async () => [
366
+ { VenueID: 123, Date: '2025-03-15' },
367
+ { VenueID: 123, Date: '2025-03-16' },
368
+ ],
369
+ });
370
+
371
+ const { container } = render(AvailabilityCalendarModal, {
372
+ props: {
373
+ show: true,
374
+ venue: defaultVenue,
375
+ onClose: vi.fn(),
376
+ onUpdate: vi.fn(),
377
+ },
378
+ });
379
+
380
+ await waitFor(() => {
381
+ expect(global.fetch).toHaveBeenCalled();
382
+ });
383
+ });
384
+
385
+ it('handles API error gracefully', async () => {
386
+ global.fetch.mockRejectedValueOnce(new Error('Network error'));
387
+
388
+ const { container } = render(AvailabilityCalendarModal, {
389
+ props: {
390
+ show: true,
391
+ venue: defaultVenue,
392
+ onClose: vi.fn(),
393
+ onUpdate: vi.fn(),
394
+ },
395
+ });
396
+
397
+ await waitFor(() => {
398
+ expect(container).toBeDefined();
399
+ });
400
+ });
401
+
402
+ it('handles non-ok response', async () => {
403
+ global.fetch.mockResolvedValueOnce({
404
+ ok: false,
405
+ json: async () => ({ error: 'Failed' }),
406
+ });
407
+
408
+ const { container } = render(AvailabilityCalendarModal, {
409
+ props: {
410
+ show: true,
411
+ venue: defaultVenue,
412
+ onClose: vi.fn(),
413
+ onUpdate: vi.fn(),
414
+ },
415
+ });
416
+
417
+ await waitFor(() => {
418
+ expect(container).toBeDefined();
419
+ });
420
+ });
421
+
422
+ it('filters events by venueId', async () => {
423
+ global.fetch.mockResolvedValueOnce({
424
+ ok: true,
425
+ json: async () => [
426
+ { VenueID: 123, Date: '2025-03-15' },
427
+ { VenueID: 999, Date: '2025-03-16' }, // Different venue
428
+ ],
429
+ });
430
+
431
+ const { container } = render(AvailabilityCalendarModal, {
432
+ props: {
433
+ show: true,
434
+ venue: defaultVenue,
435
+ onClose: vi.fn(),
436
+ onUpdate: vi.fn(),
437
+ },
438
+ });
439
+
440
+ await waitFor(() => {
441
+ expect(container).toBeDefined();
442
+ });
443
+ });
444
+ });
445
+
446
+ describe('Props Handling', () => {
447
+ it('handles null venue', async () => {
448
+ const { container } = render(AvailabilityCalendarModal, {
449
+ props: {
450
+ show: true,
451
+ venue: null,
452
+ onClose: vi.fn(),
453
+ onUpdate: vi.fn(),
454
+ },
455
+ });
456
+
457
+ expect(container).toBeDefined();
458
+ });
459
+
460
+ it('handles undefined venue', async () => {
461
+ const { container } = render(AvailabilityCalendarModal, {
462
+ props: {
463
+ show: true,
464
+ venue: undefined,
465
+ onClose: vi.fn(),
466
+ onUpdate: vi.fn(),
467
+ },
468
+ });
469
+
470
+ expect(container).toBeDefined();
471
+ });
472
+
473
+ it('handles venueId 0', async () => {
474
+ const venue = {
475
+ ...defaultVenue,
476
+ venueId: 0,
477
+ };
478
+
479
+ const { container } = render(AvailabilityCalendarModal, {
480
+ props: {
481
+ show: true,
482
+ venue,
483
+ onClose: vi.fn(),
484
+ onUpdate: vi.fn(),
485
+ },
486
+ });
487
+
488
+ expect(container).toBeDefined();
489
+ });
490
+ });
491
+
492
+ describe('Loading State', () => {
493
+ it('shows loading skeleton while fetching', async () => {
494
+ // Delay the fetch response
495
+ global.fetch.mockImplementationOnce(() =>
496
+ new Promise((resolve) => setTimeout(() => resolve({
497
+ ok: true,
498
+ json: async () => [],
499
+ }), 500))
500
+ );
501
+
502
+ const { container } = render(AvailabilityCalendarModal, {
503
+ props: {
504
+ show: true,
505
+ venue: defaultVenue,
506
+ onClose: vi.fn(),
507
+ onUpdate: vi.fn(),
508
+ },
509
+ });
510
+
511
+ // Check for loading skeleton
512
+ await waitFor(() => {
513
+ const skeleton = container.querySelector('.loading-skeleton');
514
+ expect(skeleton || container).toBeDefined();
515
+ });
516
+ });
517
+ });
518
+
519
+ describe('Save Status Messages', () => {
520
+ it('initializes with empty message', async () => {
521
+ const { container } = render(AvailabilityCalendarModal, {
522
+ props: {
523
+ show: true,
524
+ venue: defaultVenue,
525
+ onClose: vi.fn(),
526
+ onUpdate: vi.fn(),
527
+ },
528
+ });
529
+
530
+ expect(container).toBeDefined();
531
+ });
532
+ });
533
+
534
+ describe('Cookie Handling', () => {
535
+ it('handles missing token cookie', async () => {
536
+ const originalCookie = document.cookie;
537
+ Object.defineProperty(document, 'cookie', {
538
+ writable: true,
539
+ value: '',
540
+ });
541
+
542
+ const { container } = render(AvailabilityCalendarModal, {
543
+ props: {
544
+ show: true,
545
+ venue: defaultVenue,
546
+ onClose: vi.fn(),
547
+ onUpdate: vi.fn(),
548
+ },
549
+ });
550
+
551
+ expect(container).toBeDefined();
552
+
553
+ // Restore
554
+ Object.defineProperty(document, 'cookie', {
555
+ writable: true,
556
+ value: originalCookie,
557
+ });
558
+ });
559
+ });
560
+
561
+ describe('Modal Variants', () => {
562
+ it('renders mobile modal variant', async () => {
563
+ const { container } = render(AvailabilityCalendarModal, {
564
+ props: {
565
+ show: true,
566
+ venue: defaultVenue,
567
+ onClose: vi.fn(),
568
+ onUpdate: vi.fn(),
569
+ },
570
+ });
571
+
572
+ await waitFor(() => {
573
+ const mobileModal = container.querySelector('.fullscreen-modal');
574
+ expect(mobileModal).toBeDefined();
575
+ });
576
+ });
577
+
578
+ it('renders desktop side panel', async () => {
579
+ const { container } = render(AvailabilityCalendarModal, {
580
+ props: {
581
+ show: true,
582
+ venue: defaultVenue,
583
+ onClose: vi.fn(),
584
+ onUpdate: vi.fn(),
585
+ },
586
+ });
587
+
588
+ expect(container).toBeDefined();
589
+ });
590
+ });
591
+
592
+ describe('Transitions', () => {
593
+ it('applies fade transition to modal', async () => {
594
+ const { container } = render(AvailabilityCalendarModal, {
595
+ props: {
596
+ show: true,
597
+ venue: defaultVenue,
598
+ onClose: vi.fn(),
599
+ onUpdate: vi.fn(),
600
+ },
601
+ });
602
+
603
+ expect(container).toBeDefined();
604
+ });
605
+ });
606
+ });