@getmicdrop/venue-calendar 3.3.1 → 3.3.2
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/{VenueCalendar-Xppig0q_.js → VenueCalendar-BMSfRl2d.js} +10 -13
- package/dist/{VenueCalendar-Xppig0q_.js.map → VenueCalendar-BMSfRl2d.js.map} +1 -1
- package/dist/api/api.cjs +2 -0
- package/dist/api/api.cjs.map +1 -0
- package/dist/api/api.mjs +787 -0
- package/dist/api/api.mjs.map +1 -0
- package/dist/api/client.d.ts +46 -0
- package/dist/api/events.d.ts +102 -0
- package/dist/api/index.d.ts +38 -0
- package/dist/api/orders.d.ts +104 -0
- package/dist/api/promo.d.ts +45 -0
- package/dist/api/transformers/event.d.ts +86 -0
- package/dist/api/transformers/index.d.ts +9 -0
- package/dist/api/transformers/order.d.ts +105 -0
- package/dist/api/transformers/venue.d.ts +48 -0
- package/dist/api/venues.d.ts +33 -0
- package/dist/{index-BjErG0CG.js → index-CoJaem3n.js} +2 -2
- package/dist/{index-BjErG0CG.js.map → index-CoJaem3n.js.map} +1 -1
- package/dist/venue-calendar.css +1 -1
- package/dist/venue-calendar.es.js +1 -1
- package/dist/venue-calendar.iife.js +4 -4
- package/dist/venue-calendar.iife.js.map +1 -1
- package/dist/venue-calendar.umd.js +4 -4
- package/dist/venue-calendar.umd.js.map +1 -1
- package/package.json +96 -94
- package/src/lib/api/client.ts +0 -210
- package/src/lib/api/events.ts +0 -358
- package/src/lib/api/index.ts +0 -182
- package/src/lib/api/orders.ts +0 -390
- package/src/lib/api/promo.ts +0 -164
- package/src/lib/api/transformers/event.ts +0 -248
- package/src/lib/api/transformers/index.ts +0 -29
- package/src/lib/api/transformers/order.ts +0 -207
- package/src/lib/api/transformers/venue.ts +0 -118
- package/src/lib/api/venues.ts +0 -100
- package/src/lib/utils/api.js +0 -790
- package/src/lib/utils/api.test.js +0 -1284
- package/src/lib/utils/constants.js +0 -8
- package/src/lib/utils/constants.test.js +0 -39
- package/src/lib/utils/datetime.js +0 -266
- package/src/lib/utils/datetime.test.js +0 -340
- package/src/lib/utils/event-transform.js +0 -464
- package/src/lib/utils/event-transform.test.js +0 -413
- package/src/lib/utils/logger.js +0 -105
- package/src/lib/utils/timezone.js +0 -109
- package/src/lib/utils/timezone.test.js +0 -222
- package/src/lib/utils/utils.js +0 -806
- package/src/lib/utils/utils.test.js +0 -959
- /package/{src/lib/api/types.ts → dist/api/types.d.ts} +0 -0
|
@@ -1,959 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
loadCheckoutStateFromCookies,
|
|
4
|
-
persistCheckoutState,
|
|
5
|
-
clearCheckoutCookies,
|
|
6
|
-
classNames,
|
|
7
|
-
truncateTitle,
|
|
8
|
-
generateSlug,
|
|
9
|
-
calculateTicketStatus,
|
|
10
|
-
findLowestPrice,
|
|
11
|
-
getDaysInMonth,
|
|
12
|
-
formatHour,
|
|
13
|
-
validateNegativeNumber,
|
|
14
|
-
navigate,
|
|
15
|
-
getDays,
|
|
16
|
-
getDaysNumberOptions,
|
|
17
|
-
convertToDate,
|
|
18
|
-
convertToCustomDateTimeFormat,
|
|
19
|
-
formatTimeline,
|
|
20
|
-
getDay,
|
|
21
|
-
getMonth,
|
|
22
|
-
getDateOfMonth,
|
|
23
|
-
convertToCustomDateFormat,
|
|
24
|
-
formatDateTimeWithDoors,
|
|
25
|
-
getImageUrl,
|
|
26
|
-
getDisplayAvatarPath,
|
|
27
|
-
getDisplayAvatarUrl,
|
|
28
|
-
getPerformerDisplayName,
|
|
29
|
-
setCookie,
|
|
30
|
-
getCookie,
|
|
31
|
-
removeCookie,
|
|
32
|
-
ensureOrderIdExists,
|
|
33
|
-
getClientIP,
|
|
34
|
-
getVenueDetails,
|
|
35
|
-
trackUTMSource,
|
|
36
|
-
initiateOrder,
|
|
37
|
-
getOrder,
|
|
38
|
-
createPaymentIntent,
|
|
39
|
-
validatePaymentIntent,
|
|
40
|
-
} from './utils';
|
|
41
|
-
|
|
42
|
-
describe('Checkout State Management', () => {
|
|
43
|
-
const eventID = 'test-event-123';
|
|
44
|
-
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
// Clear localStorage before each test
|
|
47
|
-
localStorage.clear();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('persistCheckoutState', () => {
|
|
51
|
-
it('stores checkout state in localStorage', () => {
|
|
52
|
-
const state = {
|
|
53
|
-
eventID,
|
|
54
|
-
quantities: { '1': 2, '2': 1 },
|
|
55
|
-
donationAmounts: { '3': '25.00' },
|
|
56
|
-
promocode: 'SAVE10',
|
|
57
|
-
promoDiscountAmount: 10,
|
|
58
|
-
tickets: [
|
|
59
|
-
{ ID: 1, name: 'GA', price: 20, type: 0 },
|
|
60
|
-
{ ID: 2, name: 'VIP', price: 50, type: 0 },
|
|
61
|
-
{ ID: 3, name: 'Donation', price: null, type: 2 },
|
|
62
|
-
],
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
persistCheckoutState(state);
|
|
66
|
-
|
|
67
|
-
const stored = JSON.parse(localStorage.getItem(`checkout-state-${eventID}`));
|
|
68
|
-
expect(stored.quantities).toEqual({ '1': 2, '2': 1 });
|
|
69
|
-
expect(stored.donationAmounts).toEqual({ '3': '25.00' });
|
|
70
|
-
expect(stored.promocode).toBe('SAVE10');
|
|
71
|
-
expect(stored.promoDiscountAmount).toBe(10);
|
|
72
|
-
expect(stored.tickets).toHaveLength(3);
|
|
73
|
-
expect(stored.updatedAt).toBeDefined();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('stores minimal ticket data to avoid bloat', () => {
|
|
77
|
-
const state = {
|
|
78
|
-
eventID,
|
|
79
|
-
quantities: { '1': 1 },
|
|
80
|
-
donationAmounts: {},
|
|
81
|
-
promocode: '',
|
|
82
|
-
promoDiscountAmount: 0,
|
|
83
|
-
tickets: [
|
|
84
|
-
{
|
|
85
|
-
ID: 1,
|
|
86
|
-
name: 'GA',
|
|
87
|
-
price: 20,
|
|
88
|
-
type: 0,
|
|
89
|
-
visibility: 0,
|
|
90
|
-
salesChannel: 0,
|
|
91
|
-
extraField: 'should be stripped',
|
|
92
|
-
anotherExtra: { nested: 'data' },
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
persistCheckoutState(state);
|
|
98
|
-
|
|
99
|
-
const stored = JSON.parse(localStorage.getItem(`checkout-state-${eventID}`));
|
|
100
|
-
expect(stored.tickets[0]).toEqual({
|
|
101
|
-
ID: 1,
|
|
102
|
-
name: 'GA',
|
|
103
|
-
price: 20,
|
|
104
|
-
type: 0,
|
|
105
|
-
visibility: 0,
|
|
106
|
-
salesChannel: 0,
|
|
107
|
-
});
|
|
108
|
-
expect(stored.tickets[0].extraField).toBeUndefined();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('includes ticket type for donation ticket detection', () => {
|
|
112
|
-
const state = {
|
|
113
|
-
eventID,
|
|
114
|
-
quantities: { '1': 1 },
|
|
115
|
-
donationAmounts: { '1': '50.00' },
|
|
116
|
-
promocode: '',
|
|
117
|
-
promoDiscountAmount: 0,
|
|
118
|
-
tickets: [
|
|
119
|
-
{ ID: 1, name: 'Donation', price: null, type: 2, visibility: 0, salesChannel: 0 },
|
|
120
|
-
],
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
persistCheckoutState(state);
|
|
124
|
-
|
|
125
|
-
const stored = JSON.parse(localStorage.getItem(`checkout-state-${eventID}`));
|
|
126
|
-
expect(stored.tickets[0].type).toBe(2);
|
|
127
|
-
expect(stored.donationAmounts['1']).toBe('50.00');
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('loadCheckoutStateFromCookies', () => {
|
|
132
|
-
it('returns empty state when no data exists', () => {
|
|
133
|
-
const result = loadCheckoutStateFromCookies(eventID);
|
|
134
|
-
|
|
135
|
-
expect(result.quantities).toEqual({});
|
|
136
|
-
expect(result.donationAmounts).toEqual({});
|
|
137
|
-
expect(result.promocode).toBe('');
|
|
138
|
-
expect(result.promoDiscountAmount).toBe(0);
|
|
139
|
-
expect(result.tickets).toEqual([]);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('loads state from localStorage', () => {
|
|
143
|
-
const state = {
|
|
144
|
-
quantities: { '1': 2 },
|
|
145
|
-
donationAmounts: { '2': '15.00' },
|
|
146
|
-
promocode: 'TEST',
|
|
147
|
-
promoDiscountAmount: 5,
|
|
148
|
-
tickets: [{ ID: 1, name: 'GA', price: 10, type: 0 }],
|
|
149
|
-
updatedAt: Date.now(),
|
|
150
|
-
};
|
|
151
|
-
localStorage.setItem(`checkout-state-${eventID}`, JSON.stringify(state));
|
|
152
|
-
|
|
153
|
-
const result = loadCheckoutStateFromCookies(eventID);
|
|
154
|
-
|
|
155
|
-
expect(result.quantities).toEqual({ '1': 2 });
|
|
156
|
-
expect(result.donationAmounts).toEqual({ '2': '15.00' });
|
|
157
|
-
expect(result.promocode).toBe('TEST');
|
|
158
|
-
expect(result.promoDiscountAmount).toBe(5);
|
|
159
|
-
expect(result.tickets).toHaveLength(1);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('handles corrupted localStorage gracefully', () => {
|
|
163
|
-
localStorage.setItem(`checkout-state-${eventID}`, 'not valid json');
|
|
164
|
-
|
|
165
|
-
const result = loadCheckoutStateFromCookies(eventID);
|
|
166
|
-
|
|
167
|
-
expect(result.quantities).toEqual({});
|
|
168
|
-
expect(result.donationAmounts).toEqual({});
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('clearCheckoutCookies', () => {
|
|
173
|
-
it('clears localStorage state', () => {
|
|
174
|
-
localStorage.setItem(`checkout-state-${eventID}`, JSON.stringify({ test: true }));
|
|
175
|
-
|
|
176
|
-
clearCheckoutCookies(eventID);
|
|
177
|
-
|
|
178
|
-
expect(localStorage.getItem(`checkout-state-${eventID}`)).toBeNull();
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe('Utility Functions', () => {
|
|
184
|
-
describe('classNames', () => {
|
|
185
|
-
it('joins class names with spaces', () => {
|
|
186
|
-
expect(classNames('foo', 'bar', 'baz')).toBe('foo bar baz');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('filters out falsy values', () => {
|
|
190
|
-
expect(classNames('foo', null, 'bar', undefined, false, 'baz', '')).toBe('foo bar baz');
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('returns empty string for no valid classes', () => {
|
|
194
|
-
expect(classNames(null, undefined, false)).toBe('');
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
describe('truncateTitle', () => {
|
|
199
|
-
it('returns full title if under max length', () => {
|
|
200
|
-
expect(truncateTitle('Hello', 10)).toBe('Hello');
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('truncates and adds ellipsis if over max length', () => {
|
|
204
|
-
expect(truncateTitle('Hello World', 5)).toBe('Hello...');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('handles exact max length', () => {
|
|
208
|
-
expect(truncateTitle('Hello', 5)).toBe('Hello');
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('generateSlug', () => {
|
|
213
|
-
it('converts to lowercase and replaces spaces with hyphens', () => {
|
|
214
|
-
expect(generateSlug('Hello World')).toBe('hello-world');
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('removes special characters', () => {
|
|
218
|
-
expect(generateSlug('Hello! World? Test.')).toBe('hello-world-test');
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('handles multiple spaces and hyphens', () => {
|
|
222
|
-
expect(generateSlug('Hello World--Test')).toBe('hello-world-test');
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('returns empty string for empty input', () => {
|
|
226
|
-
expect(generateSlug('')).toBe('');
|
|
227
|
-
expect(generateSlug(null)).toBe('');
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
describe('getDaysInMonth', () => {
|
|
232
|
-
it('returns correct days for 31-day months', () => {
|
|
233
|
-
expect(getDaysInMonth(new Date(2024, 0, 15))).toBe(31); // January
|
|
234
|
-
expect(getDaysInMonth(new Date(2024, 2, 15))).toBe(31); // March
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('returns correct days for 30-day months', () => {
|
|
238
|
-
expect(getDaysInMonth(new Date(2024, 3, 15))).toBe(30); // April
|
|
239
|
-
expect(getDaysInMonth(new Date(2024, 8, 15))).toBe(30); // September
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('returns correct days for February (leap year)', () => {
|
|
243
|
-
expect(getDaysInMonth(new Date(2024, 1, 15))).toBe(29); // 2024 is leap year
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('returns correct days for February (non-leap year)', () => {
|
|
247
|
-
expect(getDaysInMonth(new Date(2023, 1, 15))).toBe(28);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe('formatHour', () => {
|
|
252
|
-
it('formats morning hours correctly', () => {
|
|
253
|
-
expect(formatHour(9)).toBe('9 AM');
|
|
254
|
-
expect(formatHour(11)).toBe('11 AM');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('formats afternoon hours correctly', () => {
|
|
258
|
-
expect(formatHour(13)).toBe('1 PM');
|
|
259
|
-
expect(formatHour(23)).toBe('11 PM');
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it('handles noon and midnight', () => {
|
|
263
|
-
expect(formatHour(0)).toBe('12 AM');
|
|
264
|
-
expect(formatHour(12)).toBe('12 PM');
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
describe('validateNegativeNumber', () => {
|
|
269
|
-
it('returns error for empty values', () => {
|
|
270
|
-
expect(validateNegativeNumber(null)).toBe('Must be greater than zero.');
|
|
271
|
-
expect(validateNegativeNumber(undefined)).toBe('Must be greater than zero.');
|
|
272
|
-
expect(validateNegativeNumber('')).toBe('Must be greater than zero.');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('returns error for zero', () => {
|
|
276
|
-
expect(validateNegativeNumber(0)).toBe('Price must be greater than zero.');
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('returns error for negative numbers', () => {
|
|
280
|
-
expect(validateNegativeNumber(-5)).toBe('Price must be greater than zero.');
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('returns empty string for valid positive numbers', () => {
|
|
284
|
-
expect(validateNegativeNumber(10)).toBe('');
|
|
285
|
-
expect(validateNegativeNumber(0.01)).toBe('');
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('returns error for invalid number formats', () => {
|
|
289
|
-
expect(validateNegativeNumber('--')).toBe('Please enter a valid number');
|
|
290
|
-
expect(validateNegativeNumber('1.2.3')).toBe('Please enter a valid number');
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
describe('Ticket Functions', () => {
|
|
296
|
-
describe('calculateTicketStatus', () => {
|
|
297
|
-
it('returns sold_out when remainingCapacity is 0', () => {
|
|
298
|
-
expect(calculateTicketStatus({ remainingCapacity: 0, totalCapacity: 10 })).toBe('sold_out');
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it('returns sold_out when quantityRemaining is 0', () => {
|
|
302
|
-
expect(calculateTicketStatus({ quantityRemaining: 0, quantity: 10 })).toBe('sold_out');
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('returns sold_out when quantity is 0 (fallback)', () => {
|
|
306
|
-
expect(calculateTicketStatus({ quantity: 0 })).toBe('sold_out');
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('returns low when less than 10% remaining', () => {
|
|
310
|
-
expect(calculateTicketStatus({ remainingCapacity: 1, totalCapacity: 100 })).toBe('low');
|
|
311
|
-
expect(calculateTicketStatus({ quantityRemaining: 5, quantity: 100 })).toBe('low');
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('returns available when 10%+ remaining', () => {
|
|
315
|
-
expect(calculateTicketStatus({ remainingCapacity: 50, totalCapacity: 100 })).toBe('available');
|
|
316
|
-
expect(calculateTicketStatus({ quantityRemaining: 10, quantity: 20 })).toBe('available');
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('returns not_started when saleStartDate is in future', () => {
|
|
320
|
-
const futureDate = new Date(Date.now() + 86400000).toISOString(); // 1 day from now
|
|
321
|
-
expect(calculateTicketStatus({ saleStartDate: futureDate, remainingCapacity: 10 })).toBe('not_started');
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('returns ended when saleEndDate is in past', () => {
|
|
325
|
-
const pastDate = new Date(Date.now() - 86400000).toISOString(); // 1 day ago
|
|
326
|
-
expect(calculateTicketStatus({ saleEndDate: pastDate, remainingCapacity: 10 })).toBe('ended');
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
describe('findLowestPrice', () => {
|
|
331
|
-
it('returns 0 for empty array', () => {
|
|
332
|
-
expect(findLowestPrice([])).toBe(0);
|
|
333
|
-
expect(findLowestPrice(null)).toBe(0);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it('finds lowest price from tickets', () => {
|
|
337
|
-
const tickets = [
|
|
338
|
-
{ price: 50 },
|
|
339
|
-
{ price: 20 },
|
|
340
|
-
{ price: 30 },
|
|
341
|
-
];
|
|
342
|
-
expect(findLowestPrice(tickets)).toBe(20);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it('supports ticketPrice field as alternative', () => {
|
|
346
|
-
const tickets = [
|
|
347
|
-
{ ticketPrice: 50 },
|
|
348
|
-
{ ticketPrice: 15 },
|
|
349
|
-
{ ticketPrice: 30 },
|
|
350
|
-
];
|
|
351
|
-
expect(findLowestPrice(tickets)).toBe(15);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('excludes free tickets (price 0)', () => {
|
|
355
|
-
const tickets = [
|
|
356
|
-
{ price: 0 },
|
|
357
|
-
{ price: 50 },
|
|
358
|
-
{ price: 20 },
|
|
359
|
-
];
|
|
360
|
-
expect(findLowestPrice(tickets)).toBe(20);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
it('returns 0 when all tickets are free', () => {
|
|
364
|
-
const tickets = [
|
|
365
|
-
{ price: 0 },
|
|
366
|
-
{ price: 0 },
|
|
367
|
-
];
|
|
368
|
-
expect(findLowestPrice(tickets)).toBe(0);
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
describe('Donation Ticket Scenarios', () => {
|
|
374
|
-
const eventID = 'donation-event';
|
|
375
|
-
|
|
376
|
-
beforeEach(() => {
|
|
377
|
-
localStorage.clear();
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
it('persists and loads donation amounts correctly', () => {
|
|
381
|
-
const state = {
|
|
382
|
-
eventID,
|
|
383
|
-
quantities: { '1': 1, '2': 2 },
|
|
384
|
-
donationAmounts: { '2': '50.00' },
|
|
385
|
-
promocode: '',
|
|
386
|
-
promoDiscountAmount: 0,
|
|
387
|
-
tickets: [
|
|
388
|
-
{ ID: 1, name: 'GA', price: 25, type: 0, visibility: 0, salesChannel: 0 },
|
|
389
|
-
{ ID: 2, name: 'Donation', price: null, type: 2, visibility: 0, salesChannel: 0 },
|
|
390
|
-
],
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
persistCheckoutState(state);
|
|
394
|
-
const loaded = loadCheckoutStateFromCookies(eventID);
|
|
395
|
-
|
|
396
|
-
expect(loaded.donationAmounts['2']).toBe('50.00');
|
|
397
|
-
expect(loaded.tickets.find(t => t.type === 2)).toBeDefined();
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
it('handles multiple donation tickets', () => {
|
|
401
|
-
const state = {
|
|
402
|
-
eventID,
|
|
403
|
-
quantities: { '1': 1, '2': 1, '3': 1 },
|
|
404
|
-
donationAmounts: { '2': '25.00', '3': '100.00' },
|
|
405
|
-
promocode: '',
|
|
406
|
-
promoDiscountAmount: 0,
|
|
407
|
-
tickets: [
|
|
408
|
-
{ ID: 1, name: 'GA', price: 25, type: 0, visibility: 0, salesChannel: 0 },
|
|
409
|
-
{ ID: 2, name: 'Small Donation', price: null, type: 2, visibility: 0, salesChannel: 0 },
|
|
410
|
-
{ ID: 3, name: 'Large Donation', price: null, type: 2, visibility: 0, salesChannel: 0 },
|
|
411
|
-
],
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
persistCheckoutState(state);
|
|
415
|
-
const loaded = loadCheckoutStateFromCookies(eventID);
|
|
416
|
-
|
|
417
|
-
expect(loaded.donationAmounts['2']).toBe('25.00');
|
|
418
|
-
expect(loaded.donationAmounts['3']).toBe('100.00');
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
it('handles mixed regular and donation tickets', () => {
|
|
422
|
-
const state = {
|
|
423
|
-
eventID,
|
|
424
|
-
quantities: { '1': 2, '2': 1, '3': 1 },
|
|
425
|
-
donationAmounts: { '3': '75.00' },
|
|
426
|
-
promocode: 'GIVE10',
|
|
427
|
-
promoDiscountAmount: 10,
|
|
428
|
-
tickets: [
|
|
429
|
-
{ ID: 1, name: 'GA', price: 25, type: 0, visibility: 0, salesChannel: 0 },
|
|
430
|
-
{ ID: 2, name: 'VIP', price: 100, type: 0, visibility: 0, salesChannel: 0 },
|
|
431
|
-
{ ID: 3, name: 'Donation', price: null, type: 2, visibility: 0, salesChannel: 0 },
|
|
432
|
-
],
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
persistCheckoutState(state);
|
|
436
|
-
const loaded = loadCheckoutStateFromCookies(eventID);
|
|
437
|
-
|
|
438
|
-
// Regular tickets should not have donation amounts
|
|
439
|
-
expect(loaded.donationAmounts['1']).toBeUndefined();
|
|
440
|
-
expect(loaded.donationAmounts['2']).toBeUndefined();
|
|
441
|
-
// Donation ticket should have amount
|
|
442
|
-
expect(loaded.donationAmounts['3']).toBe('75.00');
|
|
443
|
-
// Promo code should apply
|
|
444
|
-
expect(loaded.promocode).toBe('GIVE10');
|
|
445
|
-
expect(loaded.promoDiscountAmount).toBe(10);
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
describe('navigate', () => {
|
|
450
|
-
let pushStateSpy;
|
|
451
|
-
let replaceStateSpy;
|
|
452
|
-
let dispatchEventSpy;
|
|
453
|
-
|
|
454
|
-
beforeEach(() => {
|
|
455
|
-
pushStateSpy = vi.spyOn(window.history, 'pushState').mockImplementation(() => {});
|
|
456
|
-
replaceStateSpy = vi.spyOn(window.history, 'replaceState').mockImplementation(() => {});
|
|
457
|
-
dispatchEventSpy = vi.spyOn(window, 'dispatchEvent').mockImplementation(() => {});
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
afterEach(() => {
|
|
461
|
-
vi.restoreAllMocks();
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it('uses pushState by default', () => {
|
|
465
|
-
navigate('/test-url');
|
|
466
|
-
expect(pushStateSpy).toHaveBeenCalledWith({}, '', '/test-url');
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
it('uses replaceState when replaceState option is true', () => {
|
|
470
|
-
navigate('/test-url', { replaceState: true });
|
|
471
|
-
expect(replaceStateSpy).toHaveBeenCalledWith({}, '', '/test-url');
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it('uses custom navigate function when provided', () => {
|
|
475
|
-
const customNavigate = vi.fn();
|
|
476
|
-
navigate('/test-url', { navigateFn: customNavigate });
|
|
477
|
-
expect(customNavigate).toHaveBeenCalledWith('/test-url', { replaceState: false });
|
|
478
|
-
expect(pushStateSpy).not.toHaveBeenCalled();
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
it('dispatches micdrop-navigate event', () => {
|
|
482
|
-
navigate('/test-url');
|
|
483
|
-
expect(dispatchEventSpy).toHaveBeenCalled();
|
|
484
|
-
});
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
describe('getDays', () => {
|
|
488
|
-
it('returns weekdays when params is "weeks"', () => {
|
|
489
|
-
const result = getDays('weeks');
|
|
490
|
-
expect(result).toHaveLength(5);
|
|
491
|
-
expect(result[0]).toEqual({ value: 'monday', label: 'Monday' });
|
|
492
|
-
expect(result[4]).toEqual({ value: 'friday', label: 'Friday' });
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
it('returns days of month with suffixes for non-weeks params', () => {
|
|
496
|
-
const result = getDays('days');
|
|
497
|
-
expect(result).toHaveLength(31);
|
|
498
|
-
expect(result[0]).toEqual({ value: '1', label: '1st' });
|
|
499
|
-
expect(result[1]).toEqual({ value: '2', label: '2nd' });
|
|
500
|
-
expect(result[2]).toEqual({ value: '3', label: '3rd' });
|
|
501
|
-
expect(result[3]).toEqual({ value: '4', label: '4th' });
|
|
502
|
-
expect(result[10]).toEqual({ value: '11', label: '11th' });
|
|
503
|
-
expect(result[11]).toEqual({ value: '12', label: '12th' });
|
|
504
|
-
expect(result[12]).toEqual({ value: '13', label: '13th' });
|
|
505
|
-
expect(result[20]).toEqual({ value: '21', label: '21st' });
|
|
506
|
-
expect(result[21]).toEqual({ value: '22', label: '22nd' });
|
|
507
|
-
expect(result[22]).toEqual({ value: '23', label: '23rd' });
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
describe('getDaysNumberOptions', () => {
|
|
512
|
-
it('returns 12 options for months', () => {
|
|
513
|
-
const result = getDaysNumberOptions('months');
|
|
514
|
-
expect(result).toHaveLength(12);
|
|
515
|
-
expect(result[0]).toEqual({ value: '1', label: '1' });
|
|
516
|
-
expect(result[11]).toEqual({ value: '12', label: '12' });
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
it('returns 10 options for weeks', () => {
|
|
520
|
-
const result = getDaysNumberOptions('weeks');
|
|
521
|
-
expect(result).toHaveLength(10);
|
|
522
|
-
expect(result[0]).toEqual({ value: '1', label: '1' });
|
|
523
|
-
expect(result[9]).toEqual({ value: '10', label: '10' });
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
it('returns 31 options for other time units', () => {
|
|
527
|
-
const result = getDaysNumberOptions('days');
|
|
528
|
-
expect(result).toHaveLength(31);
|
|
529
|
-
expect(result[0]).toEqual({ value: '1', label: '1' });
|
|
530
|
-
expect(result[30]).toEqual({ value: '31', label: '31' });
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
describe('Date Formatting Functions (deprecated)', () => {
|
|
535
|
-
describe('convertToDate', () => {
|
|
536
|
-
it('formats date and time in timezone', () => {
|
|
537
|
-
const result = convertToDate('2024-01-15T19:00:00Z', 'UTC');
|
|
538
|
-
expect(result).toContain('Mon');
|
|
539
|
-
expect(result).toContain('Jan');
|
|
540
|
-
expect(result).toContain('15');
|
|
541
|
-
expect(result).toContain('2024');
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
it('returns null for falsy input', () => {
|
|
545
|
-
expect(convertToDate(null)).toBeNull();
|
|
546
|
-
expect(convertToDate('')).toBeNull();
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
describe('convertToCustomDateTimeFormat', () => {
|
|
551
|
-
it('returns a Date object', () => {
|
|
552
|
-
const result = convertToCustomDateTimeFormat('2024-01-15T19:00:00Z');
|
|
553
|
-
expect(result).toBeInstanceOf(Date);
|
|
554
|
-
expect(result.getUTCFullYear()).toBe(2024);
|
|
555
|
-
expect(result.getUTCMonth()).toBe(0);
|
|
556
|
-
expect(result.getUTCDate()).toBe(15);
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
describe('formatTimeline', () => {
|
|
561
|
-
it('formats time correctly', () => {
|
|
562
|
-
const result = formatTimeline('2024-01-15T19:00:00Z', 'UTC');
|
|
563
|
-
expect(result).toBe('7:00 PM');
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
it('returns empty string for invalid date', () => {
|
|
567
|
-
expect(formatTimeline('invalid', 'UTC')).toBe('');
|
|
568
|
-
expect(formatTimeline(null, 'UTC')).toBe('');
|
|
569
|
-
expect(formatTimeline('', 'UTC')).toBe('');
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
describe('getDay', () => {
|
|
574
|
-
it('returns day of week', () => {
|
|
575
|
-
const result = getDay('2024-01-15T12:00:00Z', 'UTC');
|
|
576
|
-
expect(result).toBe('Mon');
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
it('returns empty for invalid input', () => {
|
|
580
|
-
expect(getDay('invalid', 'UTC')).toBe('');
|
|
581
|
-
expect(getDay(null, 'UTC')).toBe('');
|
|
582
|
-
expect(getDay('', 'UTC')).toBe('');
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
describe('getMonth', () => {
|
|
587
|
-
it('returns month name', () => {
|
|
588
|
-
const result = getMonth('2024-01-15T12:00:00Z', 'UTC');
|
|
589
|
-
expect(result).toBe('Jan');
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
it('returns empty for invalid input', () => {
|
|
593
|
-
expect(getMonth('invalid', 'UTC')).toBe('');
|
|
594
|
-
expect(getMonth(null, 'UTC')).toBe('');
|
|
595
|
-
});
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
describe('getDateOfMonth', () => {
|
|
599
|
-
it('returns day number', () => {
|
|
600
|
-
const result = getDateOfMonth('2024-01-15T12:00:00Z', 'UTC');
|
|
601
|
-
expect(result).toBe('15');
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
it('returns empty for invalid input', () => {
|
|
605
|
-
expect(getDateOfMonth('invalid', 'UTC')).toBe('');
|
|
606
|
-
expect(getDateOfMonth(null, 'UTC')).toBe('');
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
describe('convertToCustomDateFormat', () => {
|
|
611
|
-
it('returns Date with only date parts', () => {
|
|
612
|
-
const result = convertToCustomDateFormat('2024-01-15T19:00:00Z');
|
|
613
|
-
expect(result).toBeInstanceOf(Date);
|
|
614
|
-
expect(result.getFullYear()).toBe(2024);
|
|
615
|
-
expect(result.getMonth()).toBe(0);
|
|
616
|
-
expect(result.getDate()).toBe(15);
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
describe('formatDateTimeWithDoors', () => {
|
|
621
|
-
it('formats date time with doors open time', () => {
|
|
622
|
-
const result = formatDateTimeWithDoors(
|
|
623
|
-
'2024-01-15T20:00:00Z',
|
|
624
|
-
'2024-01-15T19:00:00Z',
|
|
625
|
-
'UTC'
|
|
626
|
-
);
|
|
627
|
-
expect(result).toContain('Doors:');
|
|
628
|
-
expect(result).toContain('7:00 PM');
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
it('formats without doors when not provided', () => {
|
|
632
|
-
const result = formatDateTimeWithDoors('2024-01-15T20:00:00Z', null, 'UTC');
|
|
633
|
-
expect(result).not.toContain('Doors');
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
it('returns empty for missing start', () => {
|
|
637
|
-
expect(formatDateTimeWithDoors('', null, 'UTC')).toBe('');
|
|
638
|
-
expect(formatDateTimeWithDoors(null, null, 'UTC')).toBe('');
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
describe('Image URL Functions', () => {
|
|
644
|
-
describe('getImageUrl', () => {
|
|
645
|
-
it('prepends base URL to relative paths', () => {
|
|
646
|
-
const result = getImageUrl('/images/poster.jpg');
|
|
647
|
-
expect(result).toBe('https://moxy.sfo3.cdn.digitaloceanspaces.com/images/poster.jpg');
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
it('returns absolute URLs unchanged', () => {
|
|
651
|
-
const url = 'https://example.com/image.jpg';
|
|
652
|
-
expect(getImageUrl(url)).toBe(url);
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
it('returns empty for falsy input', () => {
|
|
656
|
-
expect(getImageUrl('')).toBe('');
|
|
657
|
-
expect(getImageUrl(null)).toBe('');
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
it('supports custom base URL', () => {
|
|
661
|
-
const result = getImageUrl('/image.jpg', 'https://custom.cdn.com');
|
|
662
|
-
expect(result).toBe('https://custom.cdn.com/image.jpg');
|
|
663
|
-
});
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
describe('getDisplayAvatarPath', () => {
|
|
667
|
-
it('returns empty for null profile', () => {
|
|
668
|
-
expect(getDisplayAvatarPath(null)).toBe('');
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
it('returns avatarPosition1 for source 1', () => {
|
|
672
|
-
const profile = { profileImageSource: '1', avatarPosition1: '/avatar1.jpg' };
|
|
673
|
-
expect(getDisplayAvatarPath(profile)).toBe('/avatar1.jpg');
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
it('falls back to profileImage for source 1', () => {
|
|
677
|
-
const profile = { profileImageSource: '1', profileImage: '/profile.jpg' };
|
|
678
|
-
expect(getDisplayAvatarPath(profile)).toBe('/profile.jpg');
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
it('returns correct position for sources 2-6', () => {
|
|
682
|
-
expect(getDisplayAvatarPath({ profileImageSource: '2', avatarPosition2: '/av2.jpg' })).toBe('/av2.jpg');
|
|
683
|
-
expect(getDisplayAvatarPath({ profileImageSource: '3', avatarPosition3: '/av3.jpg' })).toBe('/av3.jpg');
|
|
684
|
-
expect(getDisplayAvatarPath({ profileImageSource: '4', avatarPosition4: '/av4.jpg' })).toBe('/av4.jpg');
|
|
685
|
-
expect(getDisplayAvatarPath({ profileImageSource: '5', avatarPosition5: '/av5.jpg' })).toBe('/av5.jpg');
|
|
686
|
-
expect(getDisplayAvatarPath({ profileImageSource: '6', avatarPosition6: '/av6.jpg' })).toBe('/av6.jpg');
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
it('defaults to position 1 for unknown source', () => {
|
|
690
|
-
const profile = { profileImageSource: '99', avatarPosition1: '/av1.jpg' };
|
|
691
|
-
expect(getDisplayAvatarPath(profile)).toBe('/av1.jpg');
|
|
692
|
-
});
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
describe('getDisplayAvatarUrl', () => {
|
|
696
|
-
it('returns full URL for avatar path', () => {
|
|
697
|
-
const profile = { profileImageSource: '1', avatarPosition1: '/avatar.jpg' };
|
|
698
|
-
const result = getDisplayAvatarUrl(profile);
|
|
699
|
-
expect(result).toBe('https://moxy.sfo3.cdn.digitaloceanspaces.com/avatar.jpg');
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
it('returns empty for null profile', () => {
|
|
703
|
-
expect(getDisplayAvatarUrl(null)).toBe('');
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
it('returns empty when no avatar path', () => {
|
|
707
|
-
expect(getDisplayAvatarUrl({})).toBe('');
|
|
708
|
-
});
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
describe('getPerformerDisplayName', () => {
|
|
713
|
-
it('returns empty for null profile', () => {
|
|
714
|
-
expect(getPerformerDisplayName(null)).toBe('');
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
it('returns stage name when useStageName is true', () => {
|
|
718
|
-
const profile = { useStageName: true, stageName: 'DJ Cool', firstName: 'John', lastName: 'Doe' };
|
|
719
|
-
expect(getPerformerDisplayName(profile)).toBe('DJ Cool');
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
it('returns full name when useStageName is false', () => {
|
|
723
|
-
const profile = { useStageName: false, stageName: 'DJ Cool', firstName: 'John', lastName: 'Doe' };
|
|
724
|
-
expect(getPerformerDisplayName(profile)).toBe('John Doe');
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
it('returns full name when useStageName is not set', () => {
|
|
728
|
-
const profile = { firstName: 'John', lastName: 'Doe' };
|
|
729
|
-
expect(getPerformerDisplayName(profile)).toBe('John Doe');
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
it('falls back to stageName when no real name', () => {
|
|
733
|
-
const profile = { stageName: 'DJ Cool' };
|
|
734
|
-
expect(getPerformerDisplayName(profile)).toBe('DJ Cool');
|
|
735
|
-
});
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
describe('Cookie Functions', () => {
|
|
739
|
-
beforeEach(() => {
|
|
740
|
-
// Clear cookies (js-cookie mock may retain state)
|
|
741
|
-
document.cookie.split(';').forEach(c => {
|
|
742
|
-
document.cookie = c.trim().split('=')[0] + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
|
743
|
-
});
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
it('setCookie and getCookie work together', () => {
|
|
747
|
-
setCookie('test-cookie', 'test-value');
|
|
748
|
-
expect(getCookie('test-cookie')).toBe('test-value');
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
it('removeCookie removes the cookie', () => {
|
|
752
|
-
setCookie('test-cookie', 'test-value');
|
|
753
|
-
removeCookie('test-cookie');
|
|
754
|
-
expect(getCookie('test-cookie')).toBeUndefined();
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
describe('ensureOrderIdExists', () => {
|
|
758
|
-
it('returns existing order ID if present', async () => {
|
|
759
|
-
setCookie('order-id-event123', 'existing-order-id');
|
|
760
|
-
const result = await ensureOrderIdExists('event123');
|
|
761
|
-
expect(result).toBe('existing-order-id');
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
it('creates new order ID if not present', async () => {
|
|
765
|
-
removeCookie('order-id-event456');
|
|
766
|
-
const result = await ensureOrderIdExists('event456');
|
|
767
|
-
expect(result).toContain('order-event456-');
|
|
768
|
-
expect(getCookie('order-id-event456')).toBe(result);
|
|
769
|
-
});
|
|
770
|
-
});
|
|
771
|
-
});
|
|
772
|
-
|
|
773
|
-
describe('API Functions', () => {
|
|
774
|
-
beforeEach(() => {
|
|
775
|
-
vi.restoreAllMocks();
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
describe('getClientIP', () => {
|
|
779
|
-
it('fetches IP from ipify', async () => {
|
|
780
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
781
|
-
ok: true,
|
|
782
|
-
json: () => Promise.resolve({ ip: '1.2.3.4' }),
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
const result = await getClientIP();
|
|
786
|
-
expect(result).toBe('1.2.3.4');
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
it('returns null on error', async () => {
|
|
790
|
-
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));
|
|
791
|
-
|
|
792
|
-
const result = await getClientIP();
|
|
793
|
-
expect(result).toBeNull();
|
|
794
|
-
});
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
describe('getVenueDetails', () => {
|
|
798
|
-
it('fetches venue details', async () => {
|
|
799
|
-
const mockVenue = { id: 123, name: 'Test Venue' };
|
|
800
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
801
|
-
ok: true,
|
|
802
|
-
json: () => Promise.resolve(mockVenue),
|
|
803
|
-
});
|
|
804
|
-
|
|
805
|
-
const result = await getVenueDetails(123);
|
|
806
|
-
expect(result).toEqual(mockVenue);
|
|
807
|
-
expect(global.fetch).toHaveBeenCalledWith('https://get-micdrop.com/api/v2/public/venues/123');
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
it('throws on API error', async () => {
|
|
811
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
812
|
-
ok: false,
|
|
813
|
-
status: 404,
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
await expect(getVenueDetails(123)).rejects.toThrow('API error: 404');
|
|
817
|
-
});
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
describe('trackUTMSource', () => {
|
|
821
|
-
beforeEach(() => {
|
|
822
|
-
// Mock window.location
|
|
823
|
-
delete window.location;
|
|
824
|
-
window.location = { search: '?utm_source=facebook' };
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
it('tracks UTM source', async () => {
|
|
828
|
-
global.fetch = vi.fn().mockResolvedValue({ ok: true });
|
|
829
|
-
|
|
830
|
-
await trackUTMSource(123);
|
|
831
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
832
|
-
'https://get-micdrop.com/api/v2/public/utm/123/facebook'
|
|
833
|
-
);
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
it('defaults to Direct when no utm_source', async () => {
|
|
837
|
-
window.location.search = '';
|
|
838
|
-
global.fetch = vi.fn().mockResolvedValue({ ok: true });
|
|
839
|
-
|
|
840
|
-
await trackUTMSource(123);
|
|
841
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
842
|
-
'https://get-micdrop.com/api/v2/public/utm/123/Direct'
|
|
843
|
-
);
|
|
844
|
-
});
|
|
845
|
-
});
|
|
846
|
-
|
|
847
|
-
describe('initiateOrder', () => {
|
|
848
|
-
it('creates order successfully', async () => {
|
|
849
|
-
global.fetch = vi.fn()
|
|
850
|
-
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ ip: '1.2.3.4' }) })
|
|
851
|
-
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ uuid: 'order-uuid-123' }) });
|
|
852
|
-
|
|
853
|
-
const result = await initiateOrder({ eventID: '123', quantities: { '1': 2 } });
|
|
854
|
-
expect(result.success).toBe(true);
|
|
855
|
-
expect(result.uuid).toBe('order-uuid-123');
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
it('returns error for failed order', async () => {
|
|
859
|
-
global.fetch = vi.fn()
|
|
860
|
-
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ ip: '1.2.3.4' }) })
|
|
861
|
-
.mockResolvedValueOnce({ ok: false, status: 400, json: () => Promise.resolve({ error: 'Invalid order' }) });
|
|
862
|
-
|
|
863
|
-
const result = await initiateOrder({ eventID: '123' });
|
|
864
|
-
expect(result.success).toBe(false);
|
|
865
|
-
expect(result.error).toBe('Invalid order');
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
it('handles inventory errors', async () => {
|
|
869
|
-
global.fetch = vi.fn()
|
|
870
|
-
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ ip: '1.2.3.4' }) })
|
|
871
|
-
.mockResolvedValueOnce({ ok: false, status: 409, json: () => Promise.resolve({ error: 'Insufficient inventory' }) });
|
|
872
|
-
|
|
873
|
-
const result = await initiateOrder({ eventID: '123' });
|
|
874
|
-
expect(result.success).toBe(false);
|
|
875
|
-
expect(result.errorType).toBe('inventory');
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
it('handles network errors', async () => {
|
|
879
|
-
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));
|
|
880
|
-
|
|
881
|
-
const result = await initiateOrder({ eventID: '123' });
|
|
882
|
-
expect(result.success).toBe(false);
|
|
883
|
-
expect(result.errorType).toBe('network');
|
|
884
|
-
});
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
describe('getOrder', () => {
|
|
888
|
-
it('fetches order successfully', async () => {
|
|
889
|
-
const mockOrder = { id: 'order-123', status: 'pending' };
|
|
890
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
891
|
-
ok: true,
|
|
892
|
-
json: () => Promise.resolve(mockOrder),
|
|
893
|
-
});
|
|
894
|
-
|
|
895
|
-
const result = await getOrder('order-123');
|
|
896
|
-
expect(result).toEqual(mockOrder);
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
it('throws on API error', async () => {
|
|
900
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
901
|
-
ok: false,
|
|
902
|
-
status: 404,
|
|
903
|
-
json: () => Promise.resolve({ error: 'Order not found' }),
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
await expect(getOrder('order-123')).rejects.toThrow('Order not found');
|
|
907
|
-
});
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
describe('createPaymentIntent', () => {
|
|
911
|
-
beforeEach(() => {
|
|
912
|
-
vi.useFakeTimers();
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
afterEach(() => {
|
|
916
|
-
vi.useRealTimers();
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
it('creates payment intent successfully', async () => {
|
|
920
|
-
global.fetch = vi.fn()
|
|
921
|
-
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ ip: '1.2.3.4' }) })
|
|
922
|
-
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ clientSecret: 'pi_secret' }) });
|
|
923
|
-
|
|
924
|
-
const promise = createPaymentIntent('cart-123', { '1': 2 });
|
|
925
|
-
vi.runAllTimers();
|
|
926
|
-
const result = await promise;
|
|
927
|
-
expect(result.clientSecret).toBe('pi_secret');
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
it('throws on payment failure', async () => {
|
|
931
|
-
global.fetch = vi.fn()
|
|
932
|
-
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ ip: '1.2.3.4' }) })
|
|
933
|
-
.mockResolvedValueOnce({ ok: false, status: 500, statusText: 'Server Error', json: () => Promise.resolve({ error: 'Payment failed' }) });
|
|
934
|
-
|
|
935
|
-
const promise = createPaymentIntent('cart-123', { '1': 2 });
|
|
936
|
-
vi.runAllTimers();
|
|
937
|
-
await expect(promise).rejects.toThrow('Payment failed');
|
|
938
|
-
});
|
|
939
|
-
});
|
|
940
|
-
|
|
941
|
-
describe('validatePaymentIntent', () => {
|
|
942
|
-
it('validates payment intent', async () => {
|
|
943
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
944
|
-
ok: true,
|
|
945
|
-
json: () => Promise.resolve({ valid: true }),
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
const result = await validatePaymentIntent('cart-123', { paymentIntentId: 'pi_123' });
|
|
949
|
-
expect(result.valid).toBe(true);
|
|
950
|
-
});
|
|
951
|
-
|
|
952
|
-
it('returns null on error', async () => {
|
|
953
|
-
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));
|
|
954
|
-
|
|
955
|
-
const result = await validatePaymentIntent('cart-123', {});
|
|
956
|
-
expect(result).toBeNull();
|
|
957
|
-
});
|
|
958
|
-
});
|
|
959
|
-
});
|