@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.
- package/dist/components/Alert/Alert.spec.js +170 -170
- package/dist/components/Badges/Badge.spec.js +103 -103
- package/dist/components/BottomSheet/BottomSheet.spec.js +127 -127
- package/dist/components/Breadcrumb/Breadcrumb.spec.js +120 -120
- package/dist/components/Button/Button.spec.js +211 -211
- package/dist/components/Button/ButtonSaveDemo.spec.js +48 -48
- package/dist/components/Calendar/Calendar.spec.js +131 -131
- package/dist/components/Calendar/QuarterView.spec.js +394 -394
- package/dist/components/Card.spec.js +47 -47
- package/dist/components/CropImage/CropImage.spec.js +216 -216
- package/dist/components/DarkModeToggle.spec.js +357 -357
- package/dist/components/ErrorDisplay.spec.js +69 -69
- package/dist/components/FormActions.spec.js +88 -88
- package/dist/components/FormValidationSummary.spec.js +203 -203
- package/dist/components/Icons/Icon.spec.js +175 -175
- package/dist/components/Icons/MoreHori.spec.js +67 -67
- package/dist/components/Icons/WarningIcon.spec.js +30 -30
- package/dist/components/Input/Input.spec.js +573 -573
- package/dist/components/Input/MultiSelect.spec.js +257 -257
- package/dist/components/Input/OTPInput.spec.js +238 -238
- package/dist/components/Input/Select.spec.js +218 -218
- package/dist/components/Layout/BottomNav.spec.js +130 -130
- package/dist/components/Layout/Header.spec.js +203 -203
- package/dist/components/Modal/ConfirmationModal.spec.js +191 -191
- package/dist/components/Modal/Modal.spec.js +95 -95
- package/dist/components/Modal/ModalStateManager.spec.js +100 -100
- package/dist/components/PageLoader.spec.js +54 -54
- package/dist/components/PasswordStrengthIndicator/PasswordStrengthIndicator.spec.js +173 -173
- package/dist/components/PlaceAutocomplete/PlaceAutocomplete.spec.js +300 -300
- package/dist/components/Spinner/Spinner.spec.js +75 -75
- package/dist/components/StatusIndicator/StatusIndicator.spec.js +129 -129
- package/dist/components/Toaster/Toaster.stories.svelte +1 -1
- package/dist/components/Toggle.spec.js +158 -158
- package/dist/components/ValidationError.spec.js +79 -79
- package/dist/components/pages/performers/AvailabilityCalendarModal.spec.js +606 -606
- package/dist/components/pages/performers/AvailabilityCalendarModal.svelte +4 -4
- package/dist/components/pages/performers/ModalShowInfo.spec.js +124 -124
- package/dist/components/pages/performers/ModalShowInfo.svelte +1 -1
- package/dist/components/pages/performers/PageBackButton.spec.js +89 -89
- package/dist/components/pages/performers/SectionHeader.spec.js +75 -75
- package/dist/components/pages/performers/ShowDetails.spec.js +166 -166
- package/dist/components/pages/performers/ShowItemCard.spec.js +793 -793
- package/dist/components/pages/performers/ShowItemCard.svelte +4 -4
- package/dist/components/pages/performers/SwitchOption.spec.js +127 -127
- package/dist/components/pages/performers/VenueInfo.spec.js +167 -167
- package/dist/components/pages/performers/VenueInfo.svelte +1 -1
- package/dist/components/pages/performers/VenueItemCard.spec.js +763 -763
- package/dist/components/pages/performers/VenueItemCard.svelte +4 -4
- package/dist/components/pages/profile/profile-form.spec.js +9 -9
- package/dist/components/pages/settings/tabs/CustomImageDropzone.svelte +3 -3
- package/dist/components/pages/shows/ShowList.spec.js +33 -33
- package/dist/components/pages/shows/TabContent.spec.js +90 -90
- package/dist/components/pages/shows/TabNavigation.spec.js +143 -143
- package/dist/config.js +5 -5
- package/dist/config.spec.js +29 -29
- package/dist/constants/formOptions.js +25 -25
- package/dist/constants/formOptions.spec.js +88 -88
- package/dist/index.js +111 -111
- package/dist/stores/auth.d.ts +9 -0
- package/dist/stores/auth.d.ts.map +1 -0
- package/dist/stores/auth.js +36 -0
- package/dist/stores/auth.spec.d.ts +2 -0
- package/dist/stores/auth.spec.d.ts.map +1 -0
- package/dist/stores/auth.spec.js +139 -0
- package/dist/stores/formDataStore.d.ts +17 -0
- package/dist/stores/formDataStore.d.ts.map +1 -0
- package/dist/stores/formDataStore.js +25 -0
- package/dist/stores/formDataStore.spec.d.ts +2 -0
- package/dist/stores/formDataStore.spec.d.ts.map +1 -0
- package/dist/stores/formDataStore.spec.js +257 -0
- package/dist/stores/formSave.d.ts +24 -0
- package/dist/stores/formSave.d.ts.map +1 -0
- package/dist/stores/formSave.js +132 -0
- package/dist/stores/formSave.spec.d.ts +2 -0
- package/dist/stores/formSave.spec.d.ts.map +1 -0
- package/dist/stores/formSave.spec.js +296 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +0 -0
- package/dist/stores/navigation.d.ts +5 -0
- package/dist/stores/navigation.d.ts.map +1 -0
- package/dist/stores/navigation.js +12 -0
- package/dist/stores/navigation.spec.d.ts +2 -0
- package/dist/stores/navigation.spec.d.ts.map +1 -0
- package/dist/stores/navigation.spec.js +136 -0
- package/dist/stores/toaster.d.ts +4 -0
- package/dist/stores/toaster.d.ts.map +1 -0
- package/dist/stores/toaster.js +13 -0
- package/dist/stores/toaster.spec.d.ts +2 -0
- package/dist/stores/toaster.spec.d.ts.map +1 -0
- package/dist/stores/toaster.spec.js +59 -0
- package/dist/telemetry.js +357 -357
- package/dist/telemetry.server.js +211 -211
- package/dist/telemetry.server.spec.js +434 -434
- package/dist/telemetry.spec.js +660 -660
- package/dist/utils/apiConfig.js +49 -49
- package/dist/utils/apiConfig.spec.js +118 -118
- package/dist/utils/greetings.js +187 -187
- package/dist/utils/greetings.spec.js +337 -337
- package/dist/utils/imageValidation.js +121 -121
- package/dist/utils/imageValidation.spec.js +220 -220
- package/dist/utils/portal.js +25 -25
- package/dist/utils/portal.spec.js +143 -143
- package/dist/utils/utils/utils.js +323 -323
- package/dist/utils/utils/utils.spec.js +698 -698
- package/dist/utils/utils.spec.js +643 -643
- package/package.json +1 -1
|
@@ -1,763 +1,763 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, beforeAll, afterEach } from 'vitest';
|
|
2
|
-
import { render, fireEvent, waitFor, cleanup } 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 $app modules
|
|
12
|
-
vi.mock('$app/navigation', () => ({
|
|
13
|
-
goto: vi.fn(),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
vi.mock('$app/stores', () => ({
|
|
17
|
-
page: {
|
|
18
|
-
subscribe: vi.fn((callback) => {
|
|
19
|
-
callback({ url: new URL('http://localhost:3000/availability') });
|
|
20
|
-
return () => {};
|
|
21
|
-
}),
|
|
22
|
-
},
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
vi.mock('$app/environment', () => ({
|
|
26
|
-
browser: true,
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
// Mock stores
|
|
30
|
-
vi.mock('@/stores/toaster', () => ({
|
|
31
|
-
showToast: vi.fn(),
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
// Mock utils
|
|
35
|
-
vi.mock('@/utils/utils', () => ({
|
|
36
|
-
getPerformerToken: vi.fn(() => 'mock-token'),
|
|
37
|
-
microphonePlaceholder: '/placeholder.jpg',
|
|
38
|
-
parseLocation: vi.fn((location) => ({
|
|
39
|
-
street: location ? '123 Test St' : '',
|
|
40
|
-
cityStateZip: location ? 'City, ST 12345' : '',
|
|
41
|
-
})),
|
|
42
|
-
getUserDetails: vi.fn(() => ({ firstName: 'Test', lastName: 'User' })),
|
|
43
|
-
classNames: vi.fn((...args) => args.filter(Boolean).join(' ')),
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
|
-
// Mock Modal to always render its slots (for testing modal content)
|
|
47
|
-
// This is scoped to this test file only
|
|
48
|
-
vi.mock('@/components/Modal/Modal.svelte', async () => {
|
|
49
|
-
const MockModal = await import('../../../__mocks__/Modal.svelte');
|
|
50
|
-
return { default: MockModal.default };
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
vi.mock('@/utils/utils/utils', () => ({
|
|
55
|
-
timeAgo: vi.fn(() => '2 hours ago'),
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
vi.mock('@/utils/apiConfig', () => ({
|
|
59
|
-
buildApiUrl: vi.fn((endpoint) => `http://localhost:8080${endpoint}`),
|
|
60
|
-
API_ENDPOINTS: {
|
|
61
|
-
REMOVE_VENUE_FROM_ROSTER: '/api/performer/removeVenueFromRoster',
|
|
62
|
-
},
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
// Mock fetch globally
|
|
66
|
-
global.fetch = vi.fn();
|
|
67
|
-
|
|
68
|
-
// Mock window.location.reload
|
|
69
|
-
const mockReload = vi.fn();
|
|
70
|
-
Object.defineProperty(window, 'location', {
|
|
71
|
-
value: { reload: mockReload },
|
|
72
|
-
writable: true,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('VenueItemCard', () => {
|
|
76
|
-
let VenueItemCard;
|
|
77
|
-
let showToast;
|
|
78
|
-
|
|
79
|
-
const defaultProps = {
|
|
80
|
-
venueId: 1,
|
|
81
|
-
name: 'Test Venue',
|
|
82
|
-
image: '/test-image.jpg',
|
|
83
|
-
location: '123 Test St, City',
|
|
84
|
-
start: '2025-01-01',
|
|
85
|
-
end: '2025-12-31',
|
|
86
|
-
doorOpen: '6:00 PM',
|
|
87
|
-
email: 'test@venue.com',
|
|
88
|
-
phone: '555-1234',
|
|
89
|
-
description: 'A great venue',
|
|
90
|
-
instagram: '@testvenue',
|
|
91
|
-
facebook: 'testvenue',
|
|
92
|
-
twitter: '@testvenue',
|
|
93
|
-
inviteID: 123,
|
|
94
|
-
lastUpdated: '2025-06-01T12:00:00Z',
|
|
95
|
-
timeRange: '6:00 PM - 2:00 AM',
|
|
96
|
-
dateRange: 'Jan 1 - Dec 31',
|
|
97
|
-
status: 'upcoming',
|
|
98
|
-
invitationAccepted: true,
|
|
99
|
-
hasAvailability: true,
|
|
100
|
-
onUpdateClick: vi.fn(),
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
beforeAll(async () => {
|
|
104
|
-
vi.resetModules();
|
|
105
|
-
const toasterModule = await import('../../../../stores/toaster');
|
|
106
|
-
showToast = toasterModule.showToast;
|
|
107
|
-
VenueItemCard = (await import('./VenueItemCard.svelte')).default;
|
|
108
|
-
}, 60000);
|
|
109
|
-
|
|
110
|
-
beforeEach(() => {
|
|
111
|
-
vi.clearAllMocks();
|
|
112
|
-
global.fetch.mockReset();
|
|
113
|
-
mockReload.mockReset();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
afterEach(() => {
|
|
117
|
-
cleanup();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('Basic Rendering', () => {
|
|
121
|
-
it('renders the venue card with basic info', async () => {
|
|
122
|
-
const { container } = render(VenueItemCard, { props: defaultProps });
|
|
123
|
-
expect(container.querySelector('.rounded-lg')).toBeDefined();
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('displays venue name through VenueInfo component', async () => {
|
|
127
|
-
const { container } = render(VenueItemCard, { props: defaultProps });
|
|
128
|
-
expect(container).toBeDefined();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('renders with all props', async () => {
|
|
132
|
-
const { container } = render(VenueItemCard, { props: defaultProps });
|
|
133
|
-
expect(container.innerHTML).toBeDefined();
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe('Status: Upcoming with Availability', () => {
|
|
138
|
-
it('shows Update button when status is upcoming and has availability', async () => {
|
|
139
|
-
const { container } = render(VenueItemCard, {
|
|
140
|
-
props: {
|
|
141
|
-
...defaultProps,
|
|
142
|
-
status: 'upcoming',
|
|
143
|
-
hasAvailability: true,
|
|
144
|
-
invitationAccepted: true,
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
await waitFor(() => {
|
|
149
|
-
const updateButton = container.querySelector('button');
|
|
150
|
-
expect(updateButton).toBeDefined();
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('shows last updated text', async () => {
|
|
155
|
-
const { container } = render(VenueItemCard, {
|
|
156
|
-
props: {
|
|
157
|
-
...defaultProps,
|
|
158
|
-
status: 'upcoming',
|
|
159
|
-
hasAvailability: true,
|
|
160
|
-
invitationAccepted: true,
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
await waitFor(() => {
|
|
165
|
-
expect(container.innerHTML).toContain('Last updated');
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('calls onUpdateClick when Update button is clicked', async () => {
|
|
170
|
-
const mockUpdateClick = vi.fn();
|
|
171
|
-
const { container } = render(VenueItemCard, {
|
|
172
|
-
props: {
|
|
173
|
-
...defaultProps,
|
|
174
|
-
status: 'upcoming',
|
|
175
|
-
hasAvailability: true,
|
|
176
|
-
invitationAccepted: true,
|
|
177
|
-
onUpdateClick: mockUpdateClick,
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
await waitFor(() => {
|
|
182
|
-
const buttons = container.querySelectorAll('button');
|
|
183
|
-
const updateButton = Array.from(buttons).find(
|
|
184
|
-
(btn) => btn.textContent?.includes('Update')
|
|
185
|
-
);
|
|
186
|
-
if (updateButton) {
|
|
187
|
-
fireEvent.click(updateButton);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// The onUpdateClick should have been called
|
|
192
|
-
expect(mockUpdateClick).toBeDefined();
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
describe('Status: Upcoming without Availability', () => {
|
|
197
|
-
it('shows Set up button when invitation accepted but no availability', async () => {
|
|
198
|
-
const { container } = render(VenueItemCard, {
|
|
199
|
-
props: {
|
|
200
|
-
...defaultProps,
|
|
201
|
-
status: 'upcoming',
|
|
202
|
-
hasAvailability: false,
|
|
203
|
-
invitationAccepted: true,
|
|
204
|
-
},
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
await waitFor(() => {
|
|
208
|
-
expect(container.innerHTML).toContain('Set up');
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('shows warning message when needs availability update', async () => {
|
|
213
|
-
const { container } = render(VenueItemCard, {
|
|
214
|
-
props: {
|
|
215
|
-
...defaultProps,
|
|
216
|
-
status: 'upcoming',
|
|
217
|
-
hasAvailability: false,
|
|
218
|
-
invitationAccepted: true,
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
await waitFor(() => {
|
|
223
|
-
expect(container.innerHTML).toContain('Please update your availability');
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('calls onUpdateClick when Set up button is clicked', async () => {
|
|
228
|
-
const mockUpdateClick = vi.fn();
|
|
229
|
-
const { container } = render(VenueItemCard, {
|
|
230
|
-
props: {
|
|
231
|
-
...defaultProps,
|
|
232
|
-
status: 'upcoming',
|
|
233
|
-
hasAvailability: false,
|
|
234
|
-
invitationAccepted: true,
|
|
235
|
-
onUpdateClick: mockUpdateClick,
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
await waitFor(() => {
|
|
240
|
-
const buttons = container.querySelectorAll('button');
|
|
241
|
-
const setupButton = Array.from(buttons).find(
|
|
242
|
-
(btn) => btn.textContent?.includes('Set up')
|
|
243
|
-
);
|
|
244
|
-
if (setupButton) {
|
|
245
|
-
fireEvent.click(setupButton);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe('Status: Other States', () => {
|
|
252
|
-
it('does not show buttons when status is not upcoming', async () => {
|
|
253
|
-
const { container } = render(VenueItemCard, {
|
|
254
|
-
props: {
|
|
255
|
-
...defaultProps,
|
|
256
|
-
status: 'past',
|
|
257
|
-
hasAvailability: true,
|
|
258
|
-
invitationAccepted: true,
|
|
259
|
-
},
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
await waitFor(() => {
|
|
263
|
-
// Should not show the Update or Set up sections
|
|
264
|
-
expect(container.innerHTML).not.toContain('Last updated');
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('does not show buttons when invitation not accepted', async () => {
|
|
269
|
-
const { container } = render(VenueItemCard, {
|
|
270
|
-
props: {
|
|
271
|
-
...defaultProps,
|
|
272
|
-
status: 'upcoming',
|
|
273
|
-
hasAvailability: false,
|
|
274
|
-
invitationAccepted: false,
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
await waitFor(() => {
|
|
279
|
-
// Should not show the availability sections
|
|
280
|
-
expect(container.innerHTML).not.toContain('Please update your availability');
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
describe('Leave Venue Modal', () => {
|
|
286
|
-
it('renders the dropdown trigger icon', async () => {
|
|
287
|
-
const { container } = render(VenueItemCard, {
|
|
288
|
-
props: {
|
|
289
|
-
...defaultProps,
|
|
290
|
-
status: 'upcoming',
|
|
291
|
-
hasAvailability: true,
|
|
292
|
-
invitationAccepted: true,
|
|
293
|
-
},
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// The dropdown trigger is present
|
|
297
|
-
expect(container.querySelector('[data-testid="dropdown-menu"]')).toBeDefined();
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('has Update button visible', async () => {
|
|
301
|
-
const { container } = render(VenueItemCard, {
|
|
302
|
-
props: {
|
|
303
|
-
...defaultProps,
|
|
304
|
-
status: 'upcoming',
|
|
305
|
-
hasAvailability: true,
|
|
306
|
-
invitationAccepted: true,
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
expect(container.innerHTML).toContain('Update');
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('has more-menu class on dropdown trigger', async () => {
|
|
314
|
-
const { container } = render(VenueItemCard, {
|
|
315
|
-
props: {
|
|
316
|
-
...defaultProps,
|
|
317
|
-
status: 'upcoming',
|
|
318
|
-
hasAvailability: true,
|
|
319
|
-
invitationAccepted: true,
|
|
320
|
-
},
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
const menuTrigger = container.querySelector('.more-menu');
|
|
324
|
-
expect(menuTrigger).toBeDefined();
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
describe('Leave Venue API', () => {
|
|
329
|
-
it('calls API when leave is confirmed - success', async () => {
|
|
330
|
-
global.fetch.mockResolvedValueOnce({
|
|
331
|
-
ok: true,
|
|
332
|
-
json: async () => ({ success: true }),
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const { container } = render(VenueItemCard, {
|
|
336
|
-
props: {
|
|
337
|
-
...defaultProps,
|
|
338
|
-
status: 'upcoming',
|
|
339
|
-
hasAvailability: true,
|
|
340
|
-
invitationAccepted: true,
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// Find and click Leave button (the one in the modal footer)
|
|
345
|
-
const buttons = container.querySelectorAll('button');
|
|
346
|
-
const leaveButton = Array.from(buttons).find(
|
|
347
|
-
(btn) => btn.textContent?.trim() === 'Leave'
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
if (leaveButton) {
|
|
351
|
-
await fireEvent.click(leaveButton);
|
|
352
|
-
|
|
353
|
-
await waitFor(() => {
|
|
354
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
355
|
-
expect.stringContaining('/api/performer/removeVenueFromRoster'),
|
|
356
|
-
expect.objectContaining({
|
|
357
|
-
method: 'DELETE',
|
|
358
|
-
headers: expect.objectContaining({
|
|
359
|
-
'Content-Type': 'application/json',
|
|
360
|
-
}),
|
|
361
|
-
})
|
|
362
|
-
);
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('shows success toast on successful leave', async () => {
|
|
368
|
-
global.fetch.mockResolvedValueOnce({
|
|
369
|
-
ok: true,
|
|
370
|
-
json: async () => ({ success: true }),
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
const { container } = render(VenueItemCard, {
|
|
374
|
-
props: {
|
|
375
|
-
...defaultProps,
|
|
376
|
-
status: 'upcoming',
|
|
377
|
-
hasAvailability: true,
|
|
378
|
-
invitationAccepted: true,
|
|
379
|
-
},
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
const buttons = container.querySelectorAll('button');
|
|
383
|
-
const leaveButton = Array.from(buttons).find(
|
|
384
|
-
(btn) => btn.textContent?.trim() === 'Leave'
|
|
385
|
-
);
|
|
386
|
-
|
|
387
|
-
if (leaveButton) {
|
|
388
|
-
await fireEvent.click(leaveButton);
|
|
389
|
-
|
|
390
|
-
await waitFor(() => {
|
|
391
|
-
expect(showToast).toHaveBeenCalledWith(
|
|
392
|
-
expect.stringContaining('You have left'),
|
|
393
|
-
'success'
|
|
394
|
-
);
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
it('shows error toast on failed leave', async () => {
|
|
400
|
-
global.fetch.mockResolvedValueOnce({
|
|
401
|
-
ok: false,
|
|
402
|
-
json: async () => ({ error: 'Failed' }),
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
const { container } = render(VenueItemCard, {
|
|
406
|
-
props: {
|
|
407
|
-
...defaultProps,
|
|
408
|
-
status: 'upcoming',
|
|
409
|
-
hasAvailability: true,
|
|
410
|
-
invitationAccepted: true,
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
const buttons = container.querySelectorAll('button');
|
|
415
|
-
const leaveButton = Array.from(buttons).find(
|
|
416
|
-
(btn) => btn.textContent?.trim() === 'Leave'
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
if (leaveButton) {
|
|
420
|
-
await fireEvent.click(leaveButton);
|
|
421
|
-
|
|
422
|
-
await waitFor(() => {
|
|
423
|
-
expect(showToast).toHaveBeenCalledWith(
|
|
424
|
-
expect.stringContaining('Failed to leave'),
|
|
425
|
-
'error'
|
|
426
|
-
);
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
it('reloads page after successful leave', async () => {
|
|
432
|
-
global.fetch.mockResolvedValueOnce({
|
|
433
|
-
ok: true,
|
|
434
|
-
json: async () => ({ success: true }),
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
const { container } = render(VenueItemCard, {
|
|
438
|
-
props: {
|
|
439
|
-
...defaultProps,
|
|
440
|
-
status: 'upcoming',
|
|
441
|
-
hasAvailability: true,
|
|
442
|
-
invitationAccepted: true,
|
|
443
|
-
},
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
const buttons = container.querySelectorAll('button');
|
|
447
|
-
const leaveButton = Array.from(buttons).find(
|
|
448
|
-
(btn) => btn.textContent?.trim() === 'Leave'
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
if (leaveButton) {
|
|
452
|
-
await fireEvent.click(leaveButton);
|
|
453
|
-
|
|
454
|
-
await waitFor(() => {
|
|
455
|
-
expect(mockReload).toHaveBeenCalled();
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
describe('Card Structure', () => {
|
|
462
|
-
it('has main container with border and rounded corners', async () => {
|
|
463
|
-
const { container } = render(VenueItemCard, {
|
|
464
|
-
props: {
|
|
465
|
-
...defaultProps,
|
|
466
|
-
status: 'upcoming',
|
|
467
|
-
hasAvailability: true,
|
|
468
|
-
invitationAccepted: true,
|
|
469
|
-
},
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
const mainDiv = container.querySelector('.rounded-lg');
|
|
473
|
-
expect(mainDiv).toBeDefined();
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
describe('Props Handling', () => {
|
|
478
|
-
it('renders with minimal required props', async () => {
|
|
479
|
-
const minimalProps = {
|
|
480
|
-
venueId: 1,
|
|
481
|
-
name: 'Minimal Venue',
|
|
482
|
-
status: 'upcoming',
|
|
483
|
-
invitationAccepted: false,
|
|
484
|
-
hasAvailability: false,
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
const { container } = render(VenueItemCard, { props: minimalProps });
|
|
488
|
-
expect(container).toBeDefined();
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
it('handles undefined optional props', async () => {
|
|
492
|
-
const propsWithUndefined = {
|
|
493
|
-
venueId: 1,
|
|
494
|
-
name: 'Test Venue',
|
|
495
|
-
image: undefined,
|
|
496
|
-
location: undefined,
|
|
497
|
-
status: 'upcoming',
|
|
498
|
-
invitationAccepted: true,
|
|
499
|
-
hasAvailability: true,
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
const { container } = render(VenueItemCard, { props: propsWithUndefined });
|
|
503
|
-
expect(container).toBeDefined();
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
it('handles null optional props', async () => {
|
|
507
|
-
const propsWithNull = {
|
|
508
|
-
venueId: 1,
|
|
509
|
-
name: 'Test Venue',
|
|
510
|
-
image: null,
|
|
511
|
-
location: null,
|
|
512
|
-
email: null,
|
|
513
|
-
phone: null,
|
|
514
|
-
status: 'upcoming',
|
|
515
|
-
invitationAccepted: true,
|
|
516
|
-
hasAvailability: true,
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
const { container } = render(VenueItemCard, { props: propsWithNull });
|
|
520
|
-
expect(container).toBeDefined();
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
it('handles empty string props', async () => {
|
|
524
|
-
const propsWithEmpty = {
|
|
525
|
-
venueId: 1,
|
|
526
|
-
name: '',
|
|
527
|
-
image: '',
|
|
528
|
-
location: '',
|
|
529
|
-
description: '',
|
|
530
|
-
status: 'upcoming',
|
|
531
|
-
invitationAccepted: true,
|
|
532
|
-
hasAvailability: true,
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
const { container } = render(VenueItemCard, { props: propsWithEmpty });
|
|
536
|
-
expect(container).toBeDefined();
|
|
537
|
-
});
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
describe('Dropdown Menu', () => {
|
|
541
|
-
it('renders dropdown trigger icon', async () => {
|
|
542
|
-
const { container } = render(VenueItemCard, {
|
|
543
|
-
props: {
|
|
544
|
-
...defaultProps,
|
|
545
|
-
status: 'upcoming',
|
|
546
|
-
hasAvailability: true,
|
|
547
|
-
invitationAccepted: true,
|
|
548
|
-
},
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
const dropdownTrigger = container.querySelector('[data-testid="dropdown-menu"]');
|
|
552
|
-
expect(dropdownTrigger).toBeDefined();
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
it('renders dropdown icon with correct classes', async () => {
|
|
556
|
-
const { container } = render(VenueItemCard, {
|
|
557
|
-
props: {
|
|
558
|
-
...defaultProps,
|
|
559
|
-
status: 'upcoming',
|
|
560
|
-
hasAvailability: true,
|
|
561
|
-
invitationAccepted: true,
|
|
562
|
-
},
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
const menuTrigger = container.querySelector('.more-menu');
|
|
566
|
-
expect(menuTrigger).not.toBeNull();
|
|
567
|
-
});
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
describe('Without Token', () => {
|
|
571
|
-
it('handles API call without token', async () => {
|
|
572
|
-
const { getPerformerToken } = await import('../../../../utils/utils');
|
|
573
|
-
getPerformerToken.mockReturnValueOnce(null);
|
|
574
|
-
|
|
575
|
-
global.fetch.mockResolvedValueOnce({
|
|
576
|
-
ok: true,
|
|
577
|
-
json: async () => ({ success: true }),
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
const { container } = render(VenueItemCard, {
|
|
581
|
-
props: {
|
|
582
|
-
...defaultProps,
|
|
583
|
-
status: 'upcoming',
|
|
584
|
-
hasAvailability: true,
|
|
585
|
-
invitationAccepted: true,
|
|
586
|
-
},
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
const buttons = container.querySelectorAll('button');
|
|
590
|
-
const leaveButton = Array.from(buttons).find(
|
|
591
|
-
(btn) => btn.textContent?.trim() === 'Leave'
|
|
592
|
-
);
|
|
593
|
-
|
|
594
|
-
// With the Modal mock, the Leave button is rendered
|
|
595
|
-
expect(leaveButton).toBeDefined();
|
|
596
|
-
|
|
597
|
-
if (leaveButton) {
|
|
598
|
-
await fireEvent.click(leaveButton);
|
|
599
|
-
// Give time for async operations
|
|
600
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
describe('Modal Interactions', () => {
|
|
606
|
-
it('Cancel button closes the modal', async () => {
|
|
607
|
-
const { container } = render(VenueItemCard, {
|
|
608
|
-
props: {
|
|
609
|
-
...defaultProps,
|
|
610
|
-
status: 'upcoming',
|
|
611
|
-
hasAvailability: true,
|
|
612
|
-
invitationAccepted: true,
|
|
613
|
-
},
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
// Find Cancel button in the modal footer
|
|
617
|
-
const buttons = container.querySelectorAll('button');
|
|
618
|
-
const cancelButton = Array.from(buttons).find(
|
|
619
|
-
(btn) => btn.textContent?.trim() === 'Cancel'
|
|
620
|
-
);
|
|
621
|
-
|
|
622
|
-
if (cancelButton) {
|
|
623
|
-
await fireEvent.click(cancelButton);
|
|
624
|
-
// Modal should close after cancel
|
|
625
|
-
expect(container).toBeDefined();
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
it('handles note input in leave venue modal', async () => {
|
|
630
|
-
const { container } = render(VenueItemCard, {
|
|
631
|
-
props: {
|
|
632
|
-
...defaultProps,
|
|
633
|
-
status: 'upcoming',
|
|
634
|
-
hasAvailability: true,
|
|
635
|
-
invitationAccepted: true,
|
|
636
|
-
},
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
// Find textarea for notes
|
|
640
|
-
const textarea = container.querySelector('textarea');
|
|
641
|
-
if (textarea) {
|
|
642
|
-
await fireEvent.input(textarea, { target: { value: 'Test note for leaving' } });
|
|
643
|
-
expect(textarea.value).toBe('Test note for leaving');
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
it('Leave button triggers API call from modal', async () => {
|
|
648
|
-
global.fetch.mockResolvedValueOnce({
|
|
649
|
-
ok: true,
|
|
650
|
-
json: async () => ({ success: true }),
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
const { container } = render(VenueItemCard, {
|
|
654
|
-
props: {
|
|
655
|
-
...defaultProps,
|
|
656
|
-
status: 'upcoming',
|
|
657
|
-
hasAvailability: true,
|
|
658
|
-
invitationAccepted: true,
|
|
659
|
-
},
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
// Modal only shows Leave button when showLeaveVenue is true
|
|
663
|
-
// Look for modal buttons that might be in the DOM
|
|
664
|
-
await waitFor(() => {
|
|
665
|
-
const buttons = container.querySelectorAll('button');
|
|
666
|
-
const leaveButton = Array.from(buttons).find(
|
|
667
|
-
(btn) => btn.textContent?.trim() === 'Leave'
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
if (leaveButton) {
|
|
671
|
-
fireEvent.click(leaveButton);
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
});
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
describe('Dropdown Menu Interactions', () => {
|
|
678
|
-
it('dropdown icon is clickable', async () => {
|
|
679
|
-
const { container } = render(VenueItemCard, {
|
|
680
|
-
props: {
|
|
681
|
-
...defaultProps,
|
|
682
|
-
status: 'upcoming',
|
|
683
|
-
hasAvailability: true,
|
|
684
|
-
invitationAccepted: true,
|
|
685
|
-
},
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
const dropdownIcon = container.querySelector('[data-testid="dropdown-menu"]');
|
|
689
|
-
expect(dropdownIcon).toBeDefined();
|
|
690
|
-
if (dropdownIcon) {
|
|
691
|
-
await fireEvent.click(dropdownIcon);
|
|
692
|
-
// Dropdown should be rendered
|
|
693
|
-
expect(container).toBeDefined();
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
it('shows dropdown for red warning state', async () => {
|
|
698
|
-
const { container } = render(VenueItemCard, {
|
|
699
|
-
props: {
|
|
700
|
-
...defaultProps,
|
|
701
|
-
status: 'upcoming',
|
|
702
|
-
hasAvailability: false,
|
|
703
|
-
invitationAccepted: true,
|
|
704
|
-
},
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
const dropdownIcon = container.querySelector('[data-testid="dropdown-menu"]');
|
|
708
|
-
expect(dropdownIcon).toBeDefined();
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
it('shows dropdown for normal update state', async () => {
|
|
712
|
-
const { container } = render(VenueItemCard, {
|
|
713
|
-
props: {
|
|
714
|
-
...defaultProps,
|
|
715
|
-
status: 'upcoming',
|
|
716
|
-
hasAvailability: true,
|
|
717
|
-
invitationAccepted: true,
|
|
718
|
-
},
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
const dropdownIcon = container.querySelector('[data-testid="dropdown-menu"]');
|
|
722
|
-
expect(dropdownIcon).toBeDefined();
|
|
723
|
-
});
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
describe('Toggle Modal Function', () => {
|
|
727
|
-
it('handles non-leave modal name', async () => {
|
|
728
|
-
const { container } = render(VenueItemCard, {
|
|
729
|
-
props: {
|
|
730
|
-
...defaultProps,
|
|
731
|
-
status: 'upcoming',
|
|
732
|
-
hasAvailability: true,
|
|
733
|
-
invitationAccepted: true,
|
|
734
|
-
},
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
// The toggleModal function should handle unknown modal names gracefully
|
|
738
|
-
expect(container).toBeDefined();
|
|
739
|
-
});
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
describe('Styling', () => {
|
|
743
|
-
it('has correct card container classes', async () => {
|
|
744
|
-
const { container } = render(VenueItemCard, { props: defaultProps });
|
|
745
|
-
|
|
746
|
-
const card = container.querySelector('.rounded-lg');
|
|
747
|
-
expect(card).toBeDefined();
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
it('applies red styling when needs availability update', async () => {
|
|
751
|
-
const { container } = render(VenueItemCard, {
|
|
752
|
-
props: {
|
|
753
|
-
...defaultProps,
|
|
754
|
-
status: 'upcoming',
|
|
755
|
-
hasAvailability: false,
|
|
756
|
-
invitationAccepted: true,
|
|
757
|
-
},
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
expect(container.innerHTML).toContain('red-50');
|
|
761
|
-
});
|
|
762
|
-
});
|
|
763
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, beforeAll, afterEach } from 'vitest';
|
|
2
|
+
import { render, fireEvent, waitFor, cleanup } 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 $app modules
|
|
12
|
+
vi.mock('$app/navigation', () => ({
|
|
13
|
+
goto: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('$app/stores', () => ({
|
|
17
|
+
page: {
|
|
18
|
+
subscribe: vi.fn((callback) => {
|
|
19
|
+
callback({ url: new URL('http://localhost:3000/availability') });
|
|
20
|
+
return () => {};
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('$app/environment', () => ({
|
|
26
|
+
browser: true,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Mock stores
|
|
30
|
+
vi.mock('@/stores/toaster', () => ({
|
|
31
|
+
showToast: vi.fn(),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// Mock utils
|
|
35
|
+
vi.mock('@/utils/utils', () => ({
|
|
36
|
+
getPerformerToken: vi.fn(() => 'mock-token'),
|
|
37
|
+
microphonePlaceholder: '/placeholder.jpg',
|
|
38
|
+
parseLocation: vi.fn((location) => ({
|
|
39
|
+
street: location ? '123 Test St' : '',
|
|
40
|
+
cityStateZip: location ? 'City, ST 12345' : '',
|
|
41
|
+
})),
|
|
42
|
+
getUserDetails: vi.fn(() => ({ firstName: 'Test', lastName: 'User' })),
|
|
43
|
+
classNames: vi.fn((...args) => args.filter(Boolean).join(' ')),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
// Mock Modal to always render its slots (for testing modal content)
|
|
47
|
+
// This is scoped to this test file only
|
|
48
|
+
vi.mock('@/components/Modal/Modal.svelte', async () => {
|
|
49
|
+
const MockModal = await import('../../../__mocks__/Modal.svelte');
|
|
50
|
+
return { default: MockModal.default };
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
vi.mock('@/utils/utils/utils', () => ({
|
|
55
|
+
timeAgo: vi.fn(() => '2 hours ago'),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
vi.mock('@/utils/apiConfig', () => ({
|
|
59
|
+
buildApiUrl: vi.fn((endpoint) => `http://localhost:8080${endpoint}`),
|
|
60
|
+
API_ENDPOINTS: {
|
|
61
|
+
REMOVE_VENUE_FROM_ROSTER: '/api/performer/removeVenueFromRoster',
|
|
62
|
+
},
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
// Mock fetch globally
|
|
66
|
+
global.fetch = vi.fn();
|
|
67
|
+
|
|
68
|
+
// Mock window.location.reload
|
|
69
|
+
const mockReload = vi.fn();
|
|
70
|
+
Object.defineProperty(window, 'location', {
|
|
71
|
+
value: { reload: mockReload },
|
|
72
|
+
writable: true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('VenueItemCard', () => {
|
|
76
|
+
let VenueItemCard;
|
|
77
|
+
let showToast;
|
|
78
|
+
|
|
79
|
+
const defaultProps = {
|
|
80
|
+
venueId: 1,
|
|
81
|
+
name: 'Test Venue',
|
|
82
|
+
image: '/test-image.jpg',
|
|
83
|
+
location: '123 Test St, City',
|
|
84
|
+
start: '2025-01-01',
|
|
85
|
+
end: '2025-12-31',
|
|
86
|
+
doorOpen: '6:00 PM',
|
|
87
|
+
email: 'test@venue.com',
|
|
88
|
+
phone: '555-1234',
|
|
89
|
+
description: 'A great venue',
|
|
90
|
+
instagram: '@testvenue',
|
|
91
|
+
facebook: 'testvenue',
|
|
92
|
+
twitter: '@testvenue',
|
|
93
|
+
inviteID: 123,
|
|
94
|
+
lastUpdated: '2025-06-01T12:00:00Z',
|
|
95
|
+
timeRange: '6:00 PM - 2:00 AM',
|
|
96
|
+
dateRange: 'Jan 1 - Dec 31',
|
|
97
|
+
status: 'upcoming',
|
|
98
|
+
invitationAccepted: true,
|
|
99
|
+
hasAvailability: true,
|
|
100
|
+
onUpdateClick: vi.fn(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
beforeAll(async () => {
|
|
104
|
+
vi.resetModules();
|
|
105
|
+
const toasterModule = await import('../../../../stores/toaster');
|
|
106
|
+
showToast = toasterModule.showToast;
|
|
107
|
+
VenueItemCard = (await import('./VenueItemCard.svelte')).default;
|
|
108
|
+
}, 60000);
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
vi.clearAllMocks();
|
|
112
|
+
global.fetch.mockReset();
|
|
113
|
+
mockReload.mockReset();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(() => {
|
|
117
|
+
cleanup();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('Basic Rendering', () => {
|
|
121
|
+
it('renders the venue card with basic info', async () => {
|
|
122
|
+
const { container } = render(VenueItemCard, { props: defaultProps });
|
|
123
|
+
expect(container.querySelector('.rounded-lg')).toBeDefined();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('displays venue name through VenueInfo component', async () => {
|
|
127
|
+
const { container } = render(VenueItemCard, { props: defaultProps });
|
|
128
|
+
expect(container).toBeDefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('renders with all props', async () => {
|
|
132
|
+
const { container } = render(VenueItemCard, { props: defaultProps });
|
|
133
|
+
expect(container.innerHTML).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('Status: Upcoming with Availability', () => {
|
|
138
|
+
it('shows Update button when status is upcoming and has availability', async () => {
|
|
139
|
+
const { container } = render(VenueItemCard, {
|
|
140
|
+
props: {
|
|
141
|
+
...defaultProps,
|
|
142
|
+
status: 'upcoming',
|
|
143
|
+
hasAvailability: true,
|
|
144
|
+
invitationAccepted: true,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await waitFor(() => {
|
|
149
|
+
const updateButton = container.querySelector('button');
|
|
150
|
+
expect(updateButton).toBeDefined();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('shows last updated text', async () => {
|
|
155
|
+
const { container } = render(VenueItemCard, {
|
|
156
|
+
props: {
|
|
157
|
+
...defaultProps,
|
|
158
|
+
status: 'upcoming',
|
|
159
|
+
hasAvailability: true,
|
|
160
|
+
invitationAccepted: true,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await waitFor(() => {
|
|
165
|
+
expect(container.innerHTML).toContain('Last updated');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('calls onUpdateClick when Update button is clicked', async () => {
|
|
170
|
+
const mockUpdateClick = vi.fn();
|
|
171
|
+
const { container } = render(VenueItemCard, {
|
|
172
|
+
props: {
|
|
173
|
+
...defaultProps,
|
|
174
|
+
status: 'upcoming',
|
|
175
|
+
hasAvailability: true,
|
|
176
|
+
invitationAccepted: true,
|
|
177
|
+
onUpdateClick: mockUpdateClick,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
const buttons = container.querySelectorAll('button');
|
|
183
|
+
const updateButton = Array.from(buttons).find(
|
|
184
|
+
(btn) => btn.textContent?.includes('Update')
|
|
185
|
+
);
|
|
186
|
+
if (updateButton) {
|
|
187
|
+
fireEvent.click(updateButton);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// The onUpdateClick should have been called
|
|
192
|
+
expect(mockUpdateClick).toBeDefined();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('Status: Upcoming without Availability', () => {
|
|
197
|
+
it('shows Set up button when invitation accepted but no availability', async () => {
|
|
198
|
+
const { container } = render(VenueItemCard, {
|
|
199
|
+
props: {
|
|
200
|
+
...defaultProps,
|
|
201
|
+
status: 'upcoming',
|
|
202
|
+
hasAvailability: false,
|
|
203
|
+
invitationAccepted: true,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await waitFor(() => {
|
|
208
|
+
expect(container.innerHTML).toContain('Set up');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('shows warning message when needs availability update', async () => {
|
|
213
|
+
const { container } = render(VenueItemCard, {
|
|
214
|
+
props: {
|
|
215
|
+
...defaultProps,
|
|
216
|
+
status: 'upcoming',
|
|
217
|
+
hasAvailability: false,
|
|
218
|
+
invitationAccepted: true,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await waitFor(() => {
|
|
223
|
+
expect(container.innerHTML).toContain('Please update your availability');
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('calls onUpdateClick when Set up button is clicked', async () => {
|
|
228
|
+
const mockUpdateClick = vi.fn();
|
|
229
|
+
const { container } = render(VenueItemCard, {
|
|
230
|
+
props: {
|
|
231
|
+
...defaultProps,
|
|
232
|
+
status: 'upcoming',
|
|
233
|
+
hasAvailability: false,
|
|
234
|
+
invitationAccepted: true,
|
|
235
|
+
onUpdateClick: mockUpdateClick,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await waitFor(() => {
|
|
240
|
+
const buttons = container.querySelectorAll('button');
|
|
241
|
+
const setupButton = Array.from(buttons).find(
|
|
242
|
+
(btn) => btn.textContent?.includes('Set up')
|
|
243
|
+
);
|
|
244
|
+
if (setupButton) {
|
|
245
|
+
fireEvent.click(setupButton);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('Status: Other States', () => {
|
|
252
|
+
it('does not show buttons when status is not upcoming', async () => {
|
|
253
|
+
const { container } = render(VenueItemCard, {
|
|
254
|
+
props: {
|
|
255
|
+
...defaultProps,
|
|
256
|
+
status: 'past',
|
|
257
|
+
hasAvailability: true,
|
|
258
|
+
invitationAccepted: true,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await waitFor(() => {
|
|
263
|
+
// Should not show the Update or Set up sections
|
|
264
|
+
expect(container.innerHTML).not.toContain('Last updated');
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('does not show buttons when invitation not accepted', async () => {
|
|
269
|
+
const { container } = render(VenueItemCard, {
|
|
270
|
+
props: {
|
|
271
|
+
...defaultProps,
|
|
272
|
+
status: 'upcoming',
|
|
273
|
+
hasAvailability: false,
|
|
274
|
+
invitationAccepted: false,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await waitFor(() => {
|
|
279
|
+
// Should not show the availability sections
|
|
280
|
+
expect(container.innerHTML).not.toContain('Please update your availability');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('Leave Venue Modal', () => {
|
|
286
|
+
it('renders the dropdown trigger icon', async () => {
|
|
287
|
+
const { container } = render(VenueItemCard, {
|
|
288
|
+
props: {
|
|
289
|
+
...defaultProps,
|
|
290
|
+
status: 'upcoming',
|
|
291
|
+
hasAvailability: true,
|
|
292
|
+
invitationAccepted: true,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// The dropdown trigger is present
|
|
297
|
+
expect(container.querySelector('[data-testid="dropdown-menu"]')).toBeDefined();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('has Update button visible', async () => {
|
|
301
|
+
const { container } = render(VenueItemCard, {
|
|
302
|
+
props: {
|
|
303
|
+
...defaultProps,
|
|
304
|
+
status: 'upcoming',
|
|
305
|
+
hasAvailability: true,
|
|
306
|
+
invitationAccepted: true,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
expect(container.innerHTML).toContain('Update');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('has more-menu class on dropdown trigger', async () => {
|
|
314
|
+
const { container } = render(VenueItemCard, {
|
|
315
|
+
props: {
|
|
316
|
+
...defaultProps,
|
|
317
|
+
status: 'upcoming',
|
|
318
|
+
hasAvailability: true,
|
|
319
|
+
invitationAccepted: true,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const menuTrigger = container.querySelector('.more-menu');
|
|
324
|
+
expect(menuTrigger).toBeDefined();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('Leave Venue API', () => {
|
|
329
|
+
it('calls API when leave is confirmed - success', async () => {
|
|
330
|
+
global.fetch.mockResolvedValueOnce({
|
|
331
|
+
ok: true,
|
|
332
|
+
json: async () => ({ success: true }),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const { container } = render(VenueItemCard, {
|
|
336
|
+
props: {
|
|
337
|
+
...defaultProps,
|
|
338
|
+
status: 'upcoming',
|
|
339
|
+
hasAvailability: true,
|
|
340
|
+
invitationAccepted: true,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Find and click Leave button (the one in the modal footer)
|
|
345
|
+
const buttons = container.querySelectorAll('button');
|
|
346
|
+
const leaveButton = Array.from(buttons).find(
|
|
347
|
+
(btn) => btn.textContent?.trim() === 'Leave'
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
if (leaveButton) {
|
|
351
|
+
await fireEvent.click(leaveButton);
|
|
352
|
+
|
|
353
|
+
await waitFor(() => {
|
|
354
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
355
|
+
expect.stringContaining('/api/performer/removeVenueFromRoster'),
|
|
356
|
+
expect.objectContaining({
|
|
357
|
+
method: 'DELETE',
|
|
358
|
+
headers: expect.objectContaining({
|
|
359
|
+
'Content-Type': 'application/json',
|
|
360
|
+
}),
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('shows success toast on successful leave', async () => {
|
|
368
|
+
global.fetch.mockResolvedValueOnce({
|
|
369
|
+
ok: true,
|
|
370
|
+
json: async () => ({ success: true }),
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const { container } = render(VenueItemCard, {
|
|
374
|
+
props: {
|
|
375
|
+
...defaultProps,
|
|
376
|
+
status: 'upcoming',
|
|
377
|
+
hasAvailability: true,
|
|
378
|
+
invitationAccepted: true,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const buttons = container.querySelectorAll('button');
|
|
383
|
+
const leaveButton = Array.from(buttons).find(
|
|
384
|
+
(btn) => btn.textContent?.trim() === 'Leave'
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
if (leaveButton) {
|
|
388
|
+
await fireEvent.click(leaveButton);
|
|
389
|
+
|
|
390
|
+
await waitFor(() => {
|
|
391
|
+
expect(showToast).toHaveBeenCalledWith(
|
|
392
|
+
expect.stringContaining('You have left'),
|
|
393
|
+
'success'
|
|
394
|
+
);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('shows error toast on failed leave', async () => {
|
|
400
|
+
global.fetch.mockResolvedValueOnce({
|
|
401
|
+
ok: false,
|
|
402
|
+
json: async () => ({ error: 'Failed' }),
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const { container } = render(VenueItemCard, {
|
|
406
|
+
props: {
|
|
407
|
+
...defaultProps,
|
|
408
|
+
status: 'upcoming',
|
|
409
|
+
hasAvailability: true,
|
|
410
|
+
invitationAccepted: true,
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const buttons = container.querySelectorAll('button');
|
|
415
|
+
const leaveButton = Array.from(buttons).find(
|
|
416
|
+
(btn) => btn.textContent?.trim() === 'Leave'
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
if (leaveButton) {
|
|
420
|
+
await fireEvent.click(leaveButton);
|
|
421
|
+
|
|
422
|
+
await waitFor(() => {
|
|
423
|
+
expect(showToast).toHaveBeenCalledWith(
|
|
424
|
+
expect.stringContaining('Failed to leave'),
|
|
425
|
+
'error'
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('reloads page after successful leave', async () => {
|
|
432
|
+
global.fetch.mockResolvedValueOnce({
|
|
433
|
+
ok: true,
|
|
434
|
+
json: async () => ({ success: true }),
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const { container } = render(VenueItemCard, {
|
|
438
|
+
props: {
|
|
439
|
+
...defaultProps,
|
|
440
|
+
status: 'upcoming',
|
|
441
|
+
hasAvailability: true,
|
|
442
|
+
invitationAccepted: true,
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const buttons = container.querySelectorAll('button');
|
|
447
|
+
const leaveButton = Array.from(buttons).find(
|
|
448
|
+
(btn) => btn.textContent?.trim() === 'Leave'
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
if (leaveButton) {
|
|
452
|
+
await fireEvent.click(leaveButton);
|
|
453
|
+
|
|
454
|
+
await waitFor(() => {
|
|
455
|
+
expect(mockReload).toHaveBeenCalled();
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
describe('Card Structure', () => {
|
|
462
|
+
it('has main container with border and rounded corners', async () => {
|
|
463
|
+
const { container } = render(VenueItemCard, {
|
|
464
|
+
props: {
|
|
465
|
+
...defaultProps,
|
|
466
|
+
status: 'upcoming',
|
|
467
|
+
hasAvailability: true,
|
|
468
|
+
invitationAccepted: true,
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const mainDiv = container.querySelector('.rounded-lg');
|
|
473
|
+
expect(mainDiv).toBeDefined();
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('Props Handling', () => {
|
|
478
|
+
it('renders with minimal required props', async () => {
|
|
479
|
+
const minimalProps = {
|
|
480
|
+
venueId: 1,
|
|
481
|
+
name: 'Minimal Venue',
|
|
482
|
+
status: 'upcoming',
|
|
483
|
+
invitationAccepted: false,
|
|
484
|
+
hasAvailability: false,
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const { container } = render(VenueItemCard, { props: minimalProps });
|
|
488
|
+
expect(container).toBeDefined();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('handles undefined optional props', async () => {
|
|
492
|
+
const propsWithUndefined = {
|
|
493
|
+
venueId: 1,
|
|
494
|
+
name: 'Test Venue',
|
|
495
|
+
image: undefined,
|
|
496
|
+
location: undefined,
|
|
497
|
+
status: 'upcoming',
|
|
498
|
+
invitationAccepted: true,
|
|
499
|
+
hasAvailability: true,
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const { container } = render(VenueItemCard, { props: propsWithUndefined });
|
|
503
|
+
expect(container).toBeDefined();
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('handles null optional props', async () => {
|
|
507
|
+
const propsWithNull = {
|
|
508
|
+
venueId: 1,
|
|
509
|
+
name: 'Test Venue',
|
|
510
|
+
image: null,
|
|
511
|
+
location: null,
|
|
512
|
+
email: null,
|
|
513
|
+
phone: null,
|
|
514
|
+
status: 'upcoming',
|
|
515
|
+
invitationAccepted: true,
|
|
516
|
+
hasAvailability: true,
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const { container } = render(VenueItemCard, { props: propsWithNull });
|
|
520
|
+
expect(container).toBeDefined();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it('handles empty string props', async () => {
|
|
524
|
+
const propsWithEmpty = {
|
|
525
|
+
venueId: 1,
|
|
526
|
+
name: '',
|
|
527
|
+
image: '',
|
|
528
|
+
location: '',
|
|
529
|
+
description: '',
|
|
530
|
+
status: 'upcoming',
|
|
531
|
+
invitationAccepted: true,
|
|
532
|
+
hasAvailability: true,
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const { container } = render(VenueItemCard, { props: propsWithEmpty });
|
|
536
|
+
expect(container).toBeDefined();
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
describe('Dropdown Menu', () => {
|
|
541
|
+
it('renders dropdown trigger icon', async () => {
|
|
542
|
+
const { container } = render(VenueItemCard, {
|
|
543
|
+
props: {
|
|
544
|
+
...defaultProps,
|
|
545
|
+
status: 'upcoming',
|
|
546
|
+
hasAvailability: true,
|
|
547
|
+
invitationAccepted: true,
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
const dropdownTrigger = container.querySelector('[data-testid="dropdown-menu"]');
|
|
552
|
+
expect(dropdownTrigger).toBeDefined();
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it('renders dropdown icon with correct classes', async () => {
|
|
556
|
+
const { container } = render(VenueItemCard, {
|
|
557
|
+
props: {
|
|
558
|
+
...defaultProps,
|
|
559
|
+
status: 'upcoming',
|
|
560
|
+
hasAvailability: true,
|
|
561
|
+
invitationAccepted: true,
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
const menuTrigger = container.querySelector('.more-menu');
|
|
566
|
+
expect(menuTrigger).not.toBeNull();
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
describe('Without Token', () => {
|
|
571
|
+
it('handles API call without token', async () => {
|
|
572
|
+
const { getPerformerToken } = await import('../../../../utils/utils');
|
|
573
|
+
getPerformerToken.mockReturnValueOnce(null);
|
|
574
|
+
|
|
575
|
+
global.fetch.mockResolvedValueOnce({
|
|
576
|
+
ok: true,
|
|
577
|
+
json: async () => ({ success: true }),
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const { container } = render(VenueItemCard, {
|
|
581
|
+
props: {
|
|
582
|
+
...defaultProps,
|
|
583
|
+
status: 'upcoming',
|
|
584
|
+
hasAvailability: true,
|
|
585
|
+
invitationAccepted: true,
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const buttons = container.querySelectorAll('button');
|
|
590
|
+
const leaveButton = Array.from(buttons).find(
|
|
591
|
+
(btn) => btn.textContent?.trim() === 'Leave'
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
// With the Modal mock, the Leave button is rendered
|
|
595
|
+
expect(leaveButton).toBeDefined();
|
|
596
|
+
|
|
597
|
+
if (leaveButton) {
|
|
598
|
+
await fireEvent.click(leaveButton);
|
|
599
|
+
// Give time for async operations
|
|
600
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
describe('Modal Interactions', () => {
|
|
606
|
+
it('Cancel button closes the modal', async () => {
|
|
607
|
+
const { container } = render(VenueItemCard, {
|
|
608
|
+
props: {
|
|
609
|
+
...defaultProps,
|
|
610
|
+
status: 'upcoming',
|
|
611
|
+
hasAvailability: true,
|
|
612
|
+
invitationAccepted: true,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// Find Cancel button in the modal footer
|
|
617
|
+
const buttons = container.querySelectorAll('button');
|
|
618
|
+
const cancelButton = Array.from(buttons).find(
|
|
619
|
+
(btn) => btn.textContent?.trim() === 'Cancel'
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
if (cancelButton) {
|
|
623
|
+
await fireEvent.click(cancelButton);
|
|
624
|
+
// Modal should close after cancel
|
|
625
|
+
expect(container).toBeDefined();
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('handles note input in leave venue modal', async () => {
|
|
630
|
+
const { container } = render(VenueItemCard, {
|
|
631
|
+
props: {
|
|
632
|
+
...defaultProps,
|
|
633
|
+
status: 'upcoming',
|
|
634
|
+
hasAvailability: true,
|
|
635
|
+
invitationAccepted: true,
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
// Find textarea for notes
|
|
640
|
+
const textarea = container.querySelector('textarea');
|
|
641
|
+
if (textarea) {
|
|
642
|
+
await fireEvent.input(textarea, { target: { value: 'Test note for leaving' } });
|
|
643
|
+
expect(textarea.value).toBe('Test note for leaving');
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('Leave button triggers API call from modal', async () => {
|
|
648
|
+
global.fetch.mockResolvedValueOnce({
|
|
649
|
+
ok: true,
|
|
650
|
+
json: async () => ({ success: true }),
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
const { container } = render(VenueItemCard, {
|
|
654
|
+
props: {
|
|
655
|
+
...defaultProps,
|
|
656
|
+
status: 'upcoming',
|
|
657
|
+
hasAvailability: true,
|
|
658
|
+
invitationAccepted: true,
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Modal only shows Leave button when showLeaveVenue is true
|
|
663
|
+
// Look for modal buttons that might be in the DOM
|
|
664
|
+
await waitFor(() => {
|
|
665
|
+
const buttons = container.querySelectorAll('button');
|
|
666
|
+
const leaveButton = Array.from(buttons).find(
|
|
667
|
+
(btn) => btn.textContent?.trim() === 'Leave'
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
if (leaveButton) {
|
|
671
|
+
fireEvent.click(leaveButton);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
describe('Dropdown Menu Interactions', () => {
|
|
678
|
+
it('dropdown icon is clickable', async () => {
|
|
679
|
+
const { container } = render(VenueItemCard, {
|
|
680
|
+
props: {
|
|
681
|
+
...defaultProps,
|
|
682
|
+
status: 'upcoming',
|
|
683
|
+
hasAvailability: true,
|
|
684
|
+
invitationAccepted: true,
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
const dropdownIcon = container.querySelector('[data-testid="dropdown-menu"]');
|
|
689
|
+
expect(dropdownIcon).toBeDefined();
|
|
690
|
+
if (dropdownIcon) {
|
|
691
|
+
await fireEvent.click(dropdownIcon);
|
|
692
|
+
// Dropdown should be rendered
|
|
693
|
+
expect(container).toBeDefined();
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('shows dropdown for red warning state', async () => {
|
|
698
|
+
const { container } = render(VenueItemCard, {
|
|
699
|
+
props: {
|
|
700
|
+
...defaultProps,
|
|
701
|
+
status: 'upcoming',
|
|
702
|
+
hasAvailability: false,
|
|
703
|
+
invitationAccepted: true,
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
const dropdownIcon = container.querySelector('[data-testid="dropdown-menu"]');
|
|
708
|
+
expect(dropdownIcon).toBeDefined();
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('shows dropdown for normal update state', async () => {
|
|
712
|
+
const { container } = render(VenueItemCard, {
|
|
713
|
+
props: {
|
|
714
|
+
...defaultProps,
|
|
715
|
+
status: 'upcoming',
|
|
716
|
+
hasAvailability: true,
|
|
717
|
+
invitationAccepted: true,
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
const dropdownIcon = container.querySelector('[data-testid="dropdown-menu"]');
|
|
722
|
+
expect(dropdownIcon).toBeDefined();
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe('Toggle Modal Function', () => {
|
|
727
|
+
it('handles non-leave modal name', async () => {
|
|
728
|
+
const { container } = render(VenueItemCard, {
|
|
729
|
+
props: {
|
|
730
|
+
...defaultProps,
|
|
731
|
+
status: 'upcoming',
|
|
732
|
+
hasAvailability: true,
|
|
733
|
+
invitationAccepted: true,
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
// The toggleModal function should handle unknown modal names gracefully
|
|
738
|
+
expect(container).toBeDefined();
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
describe('Styling', () => {
|
|
743
|
+
it('has correct card container classes', async () => {
|
|
744
|
+
const { container } = render(VenueItemCard, { props: defaultProps });
|
|
745
|
+
|
|
746
|
+
const card = container.querySelector('.rounded-lg');
|
|
747
|
+
expect(card).toBeDefined();
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it('applies red styling when needs availability update', async () => {
|
|
751
|
+
const { container } = render(VenueItemCard, {
|
|
752
|
+
props: {
|
|
753
|
+
...defaultProps,
|
|
754
|
+
status: 'upcoming',
|
|
755
|
+
hasAvailability: false,
|
|
756
|
+
invitationAccepted: true,
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
expect(container.innerHTML).toContain('red-50');
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
});
|