@bash-app/bash-common 30.246.0 → 30.253.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/__tests__/bashSectionTitleForBashType.test.js +32 -1
- package/dist/__tests__/bashSectionTitleForBashType.test.js.map +1 -1
- package/dist/__tests__/definitionsHelpers.test.d.ts +2 -0
- package/dist/__tests__/definitionsHelpers.test.d.ts.map +1 -0
- package/dist/__tests__/definitionsHelpers.test.js +85 -0
- package/dist/__tests__/definitionsHelpers.test.js.map +1 -0
- package/dist/__tests__/extendedSchemas.test.d.ts +2 -0
- package/dist/__tests__/extendedSchemas.test.d.ts.map +1 -0
- package/dist/__tests__/extendedSchemas.test.js +40 -0
- package/dist/__tests__/extendedSchemas.test.js.map +1 -0
- package/dist/__tests__/membershipDefinitions.test.d.ts +2 -0
- package/dist/__tests__/membershipDefinitions.test.d.ts.map +1 -0
- package/dist/__tests__/membershipDefinitions.test.js +95 -0
- package/dist/__tests__/membershipDefinitions.test.js.map +1 -0
- package/dist/__tests__/ticketAdmission.test.d.ts +5 -0
- package/dist/__tests__/ticketAdmission.test.d.ts.map +1 -0
- package/dist/__tests__/ticketAdmission.test.js +42 -0
- package/dist/__tests__/ticketAdmission.test.js.map +1 -0
- package/dist/__tests__/utmAttribution.test.d.ts +2 -0
- package/dist/__tests__/utmAttribution.test.d.ts.map +1 -0
- package/dist/__tests__/utmAttribution.test.js +92 -0
- package/dist/__tests__/utmAttribution.test.js.map +1 -0
- package/dist/definitions.d.ts +18 -5
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +84 -22
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +41 -1
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +27 -0
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/sms/smsTemplates.d.ts +27 -0
- package/dist/sms/smsTemplates.d.ts.map +1 -0
- package/dist/sms/smsTemplates.js +11 -0
- package/dist/sms/smsTemplates.js.map +1 -0
- package/dist/utils/__tests__/addressUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/addressUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/addressUtils.test.js +138 -0
- package/dist/utils/__tests__/addressUtils.test.js.map +1 -0
- package/dist/utils/__tests__/apiUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/apiUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/apiUtils.test.js +161 -0
- package/dist/utils/__tests__/apiUtils.test.js.map +1 -0
- package/dist/utils/__tests__/arrayUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/arrayUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/arrayUtils.test.js +23 -0
- package/dist/utils/__tests__/arrayUtils.test.js.map +1 -0
- package/dist/utils/__tests__/blogUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/blogUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/blogUtils.test.js +102 -0
- package/dist/utils/__tests__/blogUtils.test.js.map +1 -0
- package/dist/utils/__tests__/dateTimeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/dateTimeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/dateTimeUtils.test.js +180 -0
- package/dist/utils/__tests__/dateTimeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/discountEngine/discountCalculator.test.d.ts +2 -0
- package/dist/utils/__tests__/discountEngine/discountCalculator.test.d.ts.map +1 -0
- package/dist/utils/__tests__/discountEngine/discountCalculator.test.js +356 -0
- package/dist/utils/__tests__/discountEngine/discountCalculator.test.js.map +1 -0
- package/dist/utils/__tests__/generalDateTimeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/generalDateTimeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/generalDateTimeUtils.test.js +26 -0
- package/dist/utils/__tests__/generalDateTimeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/luxonUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/luxonUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/luxonUtils.test.js +139 -0
- package/dist/utils/__tests__/luxonUtils.test.js.map +1 -0
- package/dist/utils/__tests__/mathUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/mathUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/mathUtils.test.js +26 -0
- package/dist/utils/__tests__/mathUtils.test.js.map +1 -0
- package/dist/utils/__tests__/objUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/objUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/objUtils.test.js +41 -0
- package/dist/utils/__tests__/objUtils.test.js.map +1 -0
- package/dist/utils/__tests__/paymentUtils.test.js +48 -1
- package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
- package/dist/utils/__tests__/qrCodeUtils.test.js +16 -1
- package/dist/utils/__tests__/qrCodeUtils.test.js.map +1 -1
- package/dist/utils/__tests__/reviewUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/reviewUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/reviewUtils.test.js +49 -0
- package/dist/utils/__tests__/reviewUtils.test.js.map +1 -0
- package/dist/utils/__tests__/slugUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/slugUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/slugUtils.test.js +103 -0
- package/dist/utils/__tests__/slugUtils.test.js.map +1 -0
- package/dist/utils/__tests__/sortUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/sortUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/sortUtils.test.js +28 -0
- package/dist/utils/__tests__/sortUtils.test.js.map +1 -0
- package/dist/utils/__tests__/stringUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/stringUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/stringUtils.test.js +27 -0
- package/dist/utils/__tests__/stringUtils.test.js.map +1 -0
- package/dist/utils/__tests__/ticketListUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/ticketListUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/ticketListUtils.test.js +41 -0
- package/dist/utils/__tests__/ticketListUtils.test.js.map +1 -0
- package/dist/utils/__tests__/typeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/typeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/typeUtils.test.js +11 -0
- package/dist/utils/__tests__/typeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/userSubscriptionUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/userSubscriptionUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/userSubscriptionUtils.test.js +66 -0
- package/dist/utils/__tests__/userSubscriptionUtils.test.js.map +1 -0
- package/dist/utils/__tests__/userUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/userUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/userUtils.test.js +34 -0
- package/dist/utils/__tests__/userUtils.test.js.map +1 -0
- package/dist/utils/__tests__/venueTypeFormatter.test.d.ts +2 -0
- package/dist/utils/__tests__/venueTypeFormatter.test.d.ts.map +1 -0
- package/dist/utils/__tests__/venueTypeFormatter.test.js +25 -0
- package/dist/utils/__tests__/venueTypeFormatter.test.js.map +1 -0
- package/dist/utils/service/__tests__/regexUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/regexUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/regexUtils.test.js +51 -0
- package/dist/utils/service/__tests__/regexUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceDBUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceDBUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceDBUtils.test.js +17 -0
- package/dist/utils/service/__tests__/serviceDBUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.js +60 -0
- package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceUtils.test.js +135 -0
- package/dist/utils/service/__tests__/serviceUtils.test.js.map +1 -0
- package/dist/utils/service/serviceFeaturedSortUtils.d.ts.map +1 -1
- package/dist/utils/service/serviceFeaturedSortUtils.js +5 -1
- package/dist/utils/service/serviceFeaturedSortUtils.js.map +1 -1
- package/dist/utils/ticketListUtils.d.ts.map +1 -1
- package/dist/utils/ticketListUtils.js +11 -7
- package/dist/utils/ticketListUtils.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +47 -0
- package/src/__tests__/bashSectionTitleForBashType.test.ts +48 -1
- package/src/__tests__/definitionsHelpers.test.ts +136 -0
- package/src/__tests__/extendedSchemas.test.ts +50 -0
- package/src/__tests__/membershipDefinitions.test.ts +144 -0
- package/src/__tests__/ticketAdmission.test.ts +73 -0
- package/src/__tests__/utmAttribution.test.ts +118 -0
- package/src/definitions.ts +86 -23
- package/src/extendedSchemas.ts +35 -1
- package/src/index.ts +1 -0
- package/src/sms/smsTemplates.ts +20 -0
- package/src/utils/__tests__/addressUtils.test.ts +176 -0
- package/src/utils/__tests__/apiUtils.test.ts +219 -0
- package/src/utils/__tests__/arrayUtils.test.ts +26 -0
- package/src/utils/__tests__/blogUtils.test.ts +145 -0
- package/src/utils/__tests__/dateTimeUtils.test.ts +233 -0
- package/src/utils/__tests__/discountEngine/discountCalculator.test.ts +410 -0
- package/src/utils/__tests__/generalDateTimeUtils.test.ts +34 -0
- package/src/utils/__tests__/luxonUtils.test.ts +171 -0
- package/src/utils/__tests__/mathUtils.test.ts +30 -0
- package/src/utils/__tests__/objUtils.test.ts +48 -0
- package/src/utils/__tests__/paymentUtils.test.ts +59 -0
- package/src/utils/__tests__/qrCodeUtils.test.ts +28 -1
- package/src/utils/__tests__/reviewUtils.test.ts +65 -0
- package/src/utils/__tests__/slugUtils.test.ts +143 -0
- package/src/utils/__tests__/sortUtils.test.ts +33 -0
- package/src/utils/__tests__/stringUtils.test.ts +37 -0
- package/src/utils/__tests__/ticketListUtils.test.ts +47 -0
- package/src/utils/__tests__/typeUtils.test.ts +12 -0
- package/src/utils/__tests__/userSubscriptionUtils.test.ts +87 -0
- package/src/utils/__tests__/userUtils.test.ts +41 -0
- package/src/utils/__tests__/venueTypeFormatter.test.ts +31 -0
- package/src/utils/service/__tests__/regexUtils.test.ts +66 -0
- package/src/utils/service/__tests__/serviceDBUtils.test.ts +19 -0
- package/src/utils/service/__tests__/serviceFeaturedSortUtils.test.ts +67 -0
- package/src/utils/service/__tests__/serviceUtils.test.ts +190 -0
- package/src/utils/service/serviceFeaturedSortUtils.ts +5 -1
- package/src/utils/ticketListUtils.ts +13 -6
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
calculateTotalBashPoints,
|
|
17
17
|
calculateStripeDonationFee,
|
|
18
18
|
calculateServiceFeeWithMembershipDiscount,
|
|
19
|
+
calculatePartnerFeeWithMembershipDiscount,
|
|
19
20
|
calculateBashPlatformFeeCents,
|
|
20
21
|
splitBashPlatformFeeForTicketCheckout,
|
|
21
22
|
computeTicketApplicationFeeAmountCents,
|
|
@@ -24,7 +25,9 @@ import {
|
|
|
24
25
|
getBashPassMonthlyCapRemaining,
|
|
25
26
|
higherBashPassTier,
|
|
26
27
|
membershipGrantsBashPassTier,
|
|
28
|
+
VENUE_PRICING_PLAN_DATA,
|
|
27
29
|
} from "../paymentUtils.js";
|
|
30
|
+
import { VenuePricingPlan } from "@prisma/client";
|
|
28
31
|
|
|
29
32
|
describe("PaymentUtils - Core Functions", () => {
|
|
30
33
|
describe("Currency Conversion", () => {
|
|
@@ -337,6 +340,62 @@ describe("PaymentUtils - Core Functions", () => {
|
|
|
337
340
|
});
|
|
338
341
|
});
|
|
339
342
|
|
|
343
|
+
describe("Partner fee with membership discount", () => {
|
|
344
|
+
test("calculatePartnerFeeWithMembershipDiscount scales with amount", () => {
|
|
345
|
+
const small = calculatePartnerFeeWithMembershipDiscount(1000, MembershipTier.Basic);
|
|
346
|
+
const large = calculatePartnerFeeWithMembershipDiscount(100000, MembershipTier.Basic);
|
|
347
|
+
expect(large).toBeGreaterThan(small);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("calculatePartnerFeeWithMembershipDiscount defaults to Basic tier", () => {
|
|
351
|
+
const withTier = calculatePartnerFeeWithMembershipDiscount(5000, MembershipTier.Pro);
|
|
352
|
+
const defaulted = calculatePartnerFeeWithMembershipDiscount(5000, null);
|
|
353
|
+
expect(typeof withTier).toBe("number");
|
|
354
|
+
expect(typeof defaulted).toBe("number");
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe("VENUE_PRICING_PLAN_DATA", () => {
|
|
359
|
+
test("Percentage plan uses pay-per-booking copy and fee", () => {
|
|
360
|
+
const plan = VENUE_PRICING_PLAN_DATA[VenuePricingPlan.Percentage];
|
|
361
|
+
expect(plan.name).toBe("Pay per booking");
|
|
362
|
+
expect(plan.percentageFee).toBeGreaterThan(0);
|
|
363
|
+
expect(plan.monthlyFee).toBe(0);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("Subscription plan is flat monthly with no percentage fee", () => {
|
|
367
|
+
const plan = VENUE_PRICING_PLAN_DATA[VenuePricingPlan.Subscription];
|
|
368
|
+
expect(plan.monthlyFee).toBe(100);
|
|
369
|
+
expect(plan.percentageFee).toBe(0);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe("splitBashPlatformFeeForTicketCheckout edge cases", () => {
|
|
374
|
+
test("returns zeros when platform fee disabled", () => {
|
|
375
|
+
expect(
|
|
376
|
+
splitBashPlatformFeeForTicketCheckout({
|
|
377
|
+
grossCents: 10000,
|
|
378
|
+
platformFeeEnabled: false,
|
|
379
|
+
platformFeeRate: 0.03,
|
|
380
|
+
feeHandling: "GuestPays",
|
|
381
|
+
bashPassWaivesGuestFee: false,
|
|
382
|
+
})
|
|
383
|
+
).toEqual({ totalPlatformCents: 0, guestPlatformLineCents: 0 });
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("defaults feeHandling to HostAbsorbs", () => {
|
|
387
|
+
expect(
|
|
388
|
+
splitBashPlatformFeeForTicketCheckout({
|
|
389
|
+
grossCents: 10000,
|
|
390
|
+
platformFeeEnabled: true,
|
|
391
|
+
platformFeeRate: 0.03,
|
|
392
|
+
feeHandling: null,
|
|
393
|
+
bashPassWaivesGuestFee: false,
|
|
394
|
+
})
|
|
395
|
+
).toEqual({ totalPlatformCents: 300, guestPlatformLineCents: 0 });
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
340
399
|
describe("BashPass tier and monthly cap", () => {
|
|
341
400
|
const future = new Date(Date.now() + 86400000 * 30);
|
|
342
401
|
const past = new Date(Date.now() - 86400000);
|
|
@@ -18,12 +18,18 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Bugs in any of these route door-scanners to the wrong page (= entry chaos).
|
|
20
20
|
*/
|
|
21
|
+
import QRCode from "qrcode";
|
|
22
|
+
|
|
21
23
|
import {
|
|
22
24
|
bashEventPrismaCheckoutQrCodeUrl,
|
|
23
25
|
bashEventQrCodeUrl,
|
|
24
26
|
createQrCodeImage,
|
|
25
27
|
} from "../qrCodeUtils.js";
|
|
26
28
|
|
|
29
|
+
jest.mock("qrcode", () => ({
|
|
30
|
+
toDataURL: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
27
33
|
// ============================================================================
|
|
28
34
|
// bashEventPrismaCheckoutQrCodeUrl — checkout-id deep link
|
|
29
35
|
// ============================================================================
|
|
@@ -120,22 +126,43 @@ describe("bashEventQrCodeUrl", () => {
|
|
|
120
126
|
// ============================================================================
|
|
121
127
|
|
|
122
128
|
describe("createQrCodeImage", () => {
|
|
129
|
+
beforeEach(() => {
|
|
130
|
+
jest.clearAllMocks();
|
|
131
|
+
});
|
|
132
|
+
|
|
123
133
|
it("returns a base64-encoded PNG data URL", async () => {
|
|
134
|
+
const mockDataUrl =
|
|
135
|
+
"data:image/png;base64," +
|
|
136
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==".repeat(
|
|
137
|
+
3
|
|
138
|
+
);
|
|
139
|
+
(QRCode.toDataURL as jest.Mock).mockResolvedValue(mockDataUrl);
|
|
140
|
+
|
|
124
141
|
const dataUrl = await createQrCodeImage(
|
|
125
142
|
"https://bash.app/ticket-details/evt_1/ticket/abc"
|
|
126
143
|
);
|
|
127
144
|
expect(dataUrl).toMatch(/^data:image\/png;base64,/);
|
|
128
|
-
// Sanity: a real QR image will have *meaningful* base64 length, not a few bytes.
|
|
129
145
|
expect(dataUrl.length).toBeGreaterThan(200);
|
|
146
|
+
expect(QRCode.toDataURL).toHaveBeenCalledWith(
|
|
147
|
+
"https://bash.app/ticket-details/evt_1/ticket/abc",
|
|
148
|
+
{ errorCorrectionLevel: "high" }
|
|
149
|
+
);
|
|
130
150
|
});
|
|
131
151
|
|
|
132
152
|
it("produces a deterministic PNG for the same input (no nonce / timestamp leakage)", async () => {
|
|
153
|
+
const mockDataUrl = "data:image/png;base64,same-payload";
|
|
154
|
+
(QRCode.toDataURL as jest.Mock).mockResolvedValue(mockDataUrl);
|
|
155
|
+
|
|
133
156
|
const a = await createQrCodeImage("ticket://abc");
|
|
134
157
|
const b = await createQrCodeImage("ticket://abc");
|
|
135
158
|
expect(a).toBe(b);
|
|
136
159
|
});
|
|
137
160
|
|
|
138
161
|
it("produces DIFFERENT output for different payloads", async () => {
|
|
162
|
+
(QRCode.toDataURL as jest.Mock)
|
|
163
|
+
.mockResolvedValueOnce("data:image/png;base64,abc")
|
|
164
|
+
.mockResolvedValueOnce("data:image/png;base64,xyz");
|
|
165
|
+
|
|
139
166
|
const a = await createQrCodeImage("ticket://abc");
|
|
140
167
|
const b = await createQrCodeImage("ticket://xyz");
|
|
141
168
|
expect(a).not.toBe(b);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
calculateAverageRating,
|
|
4
|
+
generateReviewStats,
|
|
5
|
+
formatRatingWithStars,
|
|
6
|
+
isEligibleToReview,
|
|
7
|
+
} from "../reviewUtils.js";
|
|
8
|
+
import type { Review } from "../../definitions.js";
|
|
9
|
+
|
|
10
|
+
const reviews: Review[] = [
|
|
11
|
+
{ rating: 5, id: "1" } as Review,
|
|
12
|
+
{ rating: 4, id: "2" } as Review,
|
|
13
|
+
{ rating: 3, id: "3" } as Review,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
describe("reviewUtils", () => {
|
|
17
|
+
describe("calculateAverageRating", () => {
|
|
18
|
+
test("returns 0 for empty list", () => {
|
|
19
|
+
expect(calculateAverageRating([])).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("averages ratings to one decimal", () => {
|
|
23
|
+
expect(calculateAverageRating(reviews)).toBe(4);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("generateReviewStats", () => {
|
|
28
|
+
test("builds distribution and totals", () => {
|
|
29
|
+
expect(generateReviewStats(reviews)).toEqual({
|
|
30
|
+
averageRating: 4,
|
|
31
|
+
totalRatings: 3,
|
|
32
|
+
ratings: { 1: 0, 2: 0, 3: 1, 4: 1, 5: 1 },
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("ignores out-of-range ratings", () => {
|
|
37
|
+
const mixed = [{ rating: 6, id: "x" } as Review, { rating: 0, id: "y" } as Review];
|
|
38
|
+
expect(generateReviewStats(mixed).ratings).toEqual({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 });
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("formatRatingWithStars", () => {
|
|
43
|
+
test("includes full stars and half star", () => {
|
|
44
|
+
expect(formatRatingWithStars(4.5)).toBe("4.5 ★★★★½");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("rounds down fractional stars below 0.5", () => {
|
|
48
|
+
expect(formatRatingWithStars(3.2)).toBe("3.2 ★★★");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("isEligibleToReview", () => {
|
|
53
|
+
test("allows attendee who has not reviewed and is not the host", () => {
|
|
54
|
+
expect(isEligibleToReview("user-1", "event-1", "host-1", [], ["event-1"])).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("blocks host, non-attendees, and duplicate reviews", () => {
|
|
58
|
+
expect(isEligibleToReview("host-1", "event-1", "host-1", [], ["event-1"])).toBe(false);
|
|
59
|
+
expect(isEligibleToReview("user-1", "event-1", "host-1", [], [])).toBe(false);
|
|
60
|
+
expect(isEligibleToReview("user-1", "event-1", "host-1", ["event-1"], ["event-1"])).toBe(
|
|
61
|
+
false
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
BASH_DETAIL_URL,
|
|
4
|
+
BASH_EVENT_WIZARD_HOW_MAJOR_STEP,
|
|
5
|
+
BASH_EVENT_WIZARD_HOW_SUBSTEP_PAYOUT,
|
|
6
|
+
BASH_EVENT_WIZARD_PATH_PREFIX,
|
|
7
|
+
} from "../../definitions.js";
|
|
8
|
+
import {
|
|
9
|
+
generateSlug,
|
|
10
|
+
generateUniqueSlug,
|
|
11
|
+
generateBashDetailUrl,
|
|
12
|
+
getPublicBashDetailPath,
|
|
13
|
+
getBashEventWizardStepPath,
|
|
14
|
+
getBashEventWizardPayoutStepPath,
|
|
15
|
+
parseBashUrlParams,
|
|
16
|
+
validateSlug,
|
|
17
|
+
} from "../slugUtils.js";
|
|
18
|
+
|
|
19
|
+
describe("generateSlug", () => {
|
|
20
|
+
test("lowercases and slugifies a title", () => {
|
|
21
|
+
expect(generateSlug("Summer Bash 2026!")).toBe("summer-bash-2026");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("removes special characters", () => {
|
|
25
|
+
expect(generateSlug("Hello *World*")).toBe("hello-world");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("handles empty string", () => {
|
|
29
|
+
expect(generateSlug("")).toBe("");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("generateUniqueSlug", () => {
|
|
34
|
+
test("returns base slug when available", async () => {
|
|
35
|
+
const checkSlugExists = async () => false;
|
|
36
|
+
await expect(generateUniqueSlug("My Event", checkSlugExists)).resolves.toBe("my-event");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("appends counter when slug exists", async () => {
|
|
40
|
+
const taken = new Set(["my-event", "my-event-1"]);
|
|
41
|
+
const checkSlugExists = async (slug: string) => taken.has(slug);
|
|
42
|
+
await expect(generateUniqueSlug("My Event", checkSlugExists)).resolves.toBe("my-event-2");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("passes excludeId to checkSlugExists", async () => {
|
|
46
|
+
const calls: Array<{ slug: string; excludeId?: string }> = [];
|
|
47
|
+
const checkSlugExists = async (slug: string, excludeId?: string) => {
|
|
48
|
+
calls.push({ slug, excludeId });
|
|
49
|
+
return false;
|
|
50
|
+
};
|
|
51
|
+
await generateUniqueSlug("My Event", checkSlugExists, "event-123");
|
|
52
|
+
expect(calls).toEqual([{ slug: "my-event", excludeId: "event-123" }]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("generateBashDetailUrl", () => {
|
|
57
|
+
test("builds id-slug path", () => {
|
|
58
|
+
expect(generateBashDetailUrl("abc123", "summer-bash")).toBe("/bash/abc123-summer-bash");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("getPublicBashDetailPath", () => {
|
|
63
|
+
test("uses slug path when slug is present", () => {
|
|
64
|
+
expect(getPublicBashDetailPath("abc123", "summer-bash")).toBe(
|
|
65
|
+
"/bash/abc123-summer-bash"
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("trims slug before use", () => {
|
|
70
|
+
expect(getPublicBashDetailPath("abc123", " summer-bash ")).toBe(
|
|
71
|
+
"/bash/abc123-summer-bash"
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("falls back to id-only legacy path when slug missing", () => {
|
|
76
|
+
expect(getPublicBashDetailPath("abc123", null)).toBe(`${BASH_DETAIL_URL}/abc123`);
|
|
77
|
+
expect(getPublicBashDetailPath("abc123", undefined)).toBe(`${BASH_DETAIL_URL}/abc123`);
|
|
78
|
+
expect(getPublicBashDetailPath("abc123", " ")).toBe(`${BASH_DETAIL_URL}/abc123`);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("getBashEventWizardStepPath", () => {
|
|
83
|
+
test("builds wizard step path", () => {
|
|
84
|
+
expect(getBashEventWizardStepPath("evt-1", 3, 2)).toBe(
|
|
85
|
+
`${BASH_EVENT_WIZARD_PATH_PREFIX}/evt-1/3/2`
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("getBashEventWizardPayoutStepPath", () => {
|
|
91
|
+
test("returns How → Payout step path", () => {
|
|
92
|
+
expect(getBashEventWizardPayoutStepPath("evt-1")).toBe(
|
|
93
|
+
`${BASH_EVENT_WIZARD_PATH_PREFIX}/evt-1/${BASH_EVENT_WIZARD_HOW_MAJOR_STEP}/${BASH_EVENT_WIZARD_HOW_SUBSTEP_PAYOUT}`
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("parseBashUrlParams", () => {
|
|
99
|
+
test("parses id-slug format", () => {
|
|
100
|
+
expect(parseBashUrlParams("abc123-summer-bash")).toEqual({
|
|
101
|
+
id: "abc123",
|
|
102
|
+
slug: "summer-bash",
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("parses id-only legacy format", () => {
|
|
107
|
+
expect(parseBashUrlParams("abc123")).toEqual({ id: "abc123", slug: null });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("returns null for empty param", () => {
|
|
111
|
+
expect(parseBashUrlParams("")).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("returns null when id portion is empty", () => {
|
|
115
|
+
expect(parseBashUrlParams("-slug-only")).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("returns slug null when trailing dash has no slug", () => {
|
|
119
|
+
expect(parseBashUrlParams("abc123-")).toEqual({ id: "abc123", slug: null });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("uses first dash as separator only", () => {
|
|
123
|
+
expect(parseBashUrlParams("abc-my-cool-event")).toEqual({
|
|
124
|
+
id: "abc",
|
|
125
|
+
slug: "my-cool-event",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("validateSlug", () => {
|
|
131
|
+
test.each([
|
|
132
|
+
["valid-slug", true],
|
|
133
|
+
["abc123", true],
|
|
134
|
+
["a-b-c", true],
|
|
135
|
+
["UPPERCASE", false],
|
|
136
|
+
["has spaces", false],
|
|
137
|
+
["has_underscore", false],
|
|
138
|
+
["special!", false],
|
|
139
|
+
["", false],
|
|
140
|
+
])("validateSlug(%s) → %s", (slug, expected) => {
|
|
141
|
+
expect(validateSlug(slug)).toBe(expected);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for tier sort ordering.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "@jest/globals";
|
|
6
|
+
import { sortTiers } from "../sortUtils.js";
|
|
7
|
+
|
|
8
|
+
describe("sortUtils", () => {
|
|
9
|
+
describe("sortTiers", () => {
|
|
10
|
+
test("returns 0 when sortOrder matches", () => {
|
|
11
|
+
expect(sortTiers({ sortOrder: 2 }, { sortOrder: 2 })).toBe(0);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("sorts by sortOrder ascending", () => {
|
|
15
|
+
expect(sortTiers({ sortOrder: 1 }, { sortOrder: 3 })).toBeLessThan(0);
|
|
16
|
+
expect(sortTiers({ sortOrder: 5 }, { sortOrder: 2 })).toBeGreaterThan(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("falls back to price when sortOrder absent", () => {
|
|
20
|
+
expect(sortTiers({ price: 10 }, { price: 20 })).toBeLessThan(0);
|
|
21
|
+
expect(sortTiers({ price: 20 }, { price: 10 })).toBeGreaterThan(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("empty values sort before defined values", () => {
|
|
25
|
+
expect(sortTiers({}, { sortOrder: 1 })).toBeLessThan(0);
|
|
26
|
+
expect(sortTiers({ sortOrder: 1 }, {})).toBeGreaterThan(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("returns 0 when both tiers lack comparable fields", () => {
|
|
30
|
+
expect(sortTiers({}, {})).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { formatString } from "../stringUtils.js";
|
|
3
|
+
|
|
4
|
+
describe("formatString", () => {
|
|
5
|
+
test("replaces placeholders with string values", () => {
|
|
6
|
+
expect(formatString("Hello ${name}!", { name: "World" })).toBe("Hello World!");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("replaces placeholders with numeric values", () => {
|
|
10
|
+
expect(formatString("Count: ${count}", { count: 42 })).toBe("Count: 42");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("replaces multiple placeholders", () => {
|
|
14
|
+
expect(
|
|
15
|
+
formatString("${greeting}, ${name}!", { greeting: "Hi", name: "Alex" })
|
|
16
|
+
).toBe("Hi, Alex!");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("uses empty string for missing keys", () => {
|
|
20
|
+
expect(formatString("Hello ${name}!", {})).toBe("Hello !");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("leaves text without placeholders unchanged", () => {
|
|
24
|
+
expect(formatString("No placeholders here", { unused: "x" })).toBe(
|
|
25
|
+
"No placeholders here"
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("handles empty template", () => {
|
|
30
|
+
expect(formatString("", { key: "value" })).toBe("");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("only matches word-character keys", () => {
|
|
34
|
+
expect(formatString("${key_1} ${key2}", { key_1: "a", key2: "b" })).toBe("a b");
|
|
35
|
+
expect(formatString("${key-name}", {})).toBe("${key-name}");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { DateTime } from "luxon";
|
|
3
|
+
import { ticketListToString, ticketListStrToTicketList } from "../ticketListUtils.js";
|
|
4
|
+
import type { NumberOfTicketsForDate } from "../../definitions.js";
|
|
5
|
+
|
|
6
|
+
describe("ticketListUtils", () => {
|
|
7
|
+
const isoDate = "2024-07-04T18:00:00.000Z";
|
|
8
|
+
|
|
9
|
+
test("round-trips ticket list through base64 encoding", () => {
|
|
10
|
+
const ticketList = new Map<string, NumberOfTicketsForDate[]>([
|
|
11
|
+
[
|
|
12
|
+
"tier-abc",
|
|
13
|
+
[
|
|
14
|
+
{ numberOfTickets: 2, ticketDateTime: isoDate },
|
|
15
|
+
{ numberOfTickets: 1, ticketDateTime: isoDate },
|
|
16
|
+
],
|
|
17
|
+
],
|
|
18
|
+
["tier-xyz", [{ numberOfTickets: 3, ticketDateTime: isoDate }]],
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const encoded = ticketListToString(ticketList);
|
|
22
|
+
expect(encoded).not.toContain("+");
|
|
23
|
+
expect(encoded).not.toContain("/");
|
|
24
|
+
|
|
25
|
+
const decoded = ticketListStrToTicketList(encoded);
|
|
26
|
+
expect(decoded.size).toBe(2);
|
|
27
|
+
expect(decoded.get("tier-abc")).toHaveLength(2);
|
|
28
|
+
expect(decoded.get("tier-abc")![0].numberOfTickets).toBe(2);
|
|
29
|
+
expect(decoded.get("tier-xyz")![0].numberOfTickets).toBe(3);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("omits tiers with zero tickets", () => {
|
|
33
|
+
const ticketList = new Map<string, NumberOfTicketsForDate[]>([
|
|
34
|
+
["tier-empty", [{ numberOfTickets: 0, ticketDateTime: isoDate }]],
|
|
35
|
+
["tier-live", [{ numberOfTickets: 1, ticketDateTime: isoDate }]],
|
|
36
|
+
]);
|
|
37
|
+
const decoded = ticketListStrToTicketList(ticketListToString(ticketList));
|
|
38
|
+
expect(decoded.has("tier-empty")).toBe(false);
|
|
39
|
+
expect(decoded.get("tier-live")).toHaveLength(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("decodes legacy plain strings for backward compatibility", () => {
|
|
43
|
+
const plain = `tier1__2;;${DateTime.fromISO(isoDate).toISO()}~~`;
|
|
44
|
+
const decoded = ticketListStrToTicketList(plain);
|
|
45
|
+
expect(decoded.get("tier1")![0].numberOfTickets).toBe(2);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { createAllTrueObject } from "../typeUtils.js";
|
|
3
|
+
|
|
4
|
+
describe("createAllTrueObject", () => {
|
|
5
|
+
test("maps every key to true", () => {
|
|
6
|
+
expect(createAllTrueObject(["a", "b", "c"])).toEqual({ a: true, b: true, c: true });
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("returns empty object for empty key list", () => {
|
|
10
|
+
expect(createAllTrueObject([])).toEqual({});
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for service subscription tier mappings.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ServiceTypes, MembershipTier } from "@prisma/client";
|
|
6
|
+
import { describe, expect, test } from "@jest/globals";
|
|
7
|
+
import {
|
|
8
|
+
SERVICE_TIER_TO_SERVICES_LIST,
|
|
9
|
+
SERVICE_TIER_FROM_SERVICE_TYPE,
|
|
10
|
+
USER_SUBSCRIPTION_TYPES,
|
|
11
|
+
ServiceSubscriptionTier,
|
|
12
|
+
} from "../userSubscriptionUtils.js";
|
|
13
|
+
|
|
14
|
+
describe("userSubscriptionUtils", () => {
|
|
15
|
+
describe("SERVICE_TIER_TO_SERVICES_LIST", () => {
|
|
16
|
+
test("Free and Ally tiers include event and entertainment services", () => {
|
|
17
|
+
expect(SERVICE_TIER_TO_SERVICES_LIST.Free).toEqual([
|
|
18
|
+
ServiceTypes.EventServices,
|
|
19
|
+
ServiceTypes.EntertainmentServices,
|
|
20
|
+
]);
|
|
21
|
+
expect(SERVICE_TIER_TO_SERVICES_LIST.Ally).toEqual([
|
|
22
|
+
ServiceTypes.EventServices,
|
|
23
|
+
ServiceTypes.EntertainmentServices,
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("Partner and Patron tiers map to vendor and venue service groups", () => {
|
|
28
|
+
expect(SERVICE_TIER_TO_SERVICES_LIST.Partner).toEqual([
|
|
29
|
+
ServiceTypes.Vendors,
|
|
30
|
+
ServiceTypes.Exhibitors,
|
|
31
|
+
ServiceTypes.Sponsors,
|
|
32
|
+
]);
|
|
33
|
+
expect(SERVICE_TIER_TO_SERVICES_LIST.Patron).toEqual([
|
|
34
|
+
ServiceTypes.Venues,
|
|
35
|
+
ServiceTypes.Organizations,
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("SERVICE_TIER_FROM_SERVICE_TYPE", () => {
|
|
41
|
+
test("maps all service types listed in tier config", () => {
|
|
42
|
+
const configuredTypes = Object.values(SERVICE_TIER_TO_SERVICES_LIST).flat();
|
|
43
|
+
for (const serviceType of configuredTypes) {
|
|
44
|
+
expect(SERVICE_TIER_FROM_SERVICE_TYPE[serviceType]).toBeDefined();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("defaults EventServices and EntertainmentServices to Free", () => {
|
|
49
|
+
expect(SERVICE_TIER_FROM_SERVICE_TYPE[ServiceTypes.EventServices]).toBe(
|
|
50
|
+
ServiceSubscriptionTier.Free
|
|
51
|
+
);
|
|
52
|
+
expect(SERVICE_TIER_FROM_SERVICE_TYPE[ServiceTypes.EntertainmentServices]).toBe(
|
|
53
|
+
ServiceSubscriptionTier.Free
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("maps vendor-related types to Partner", () => {
|
|
58
|
+
expect(SERVICE_TIER_FROM_SERVICE_TYPE[ServiceTypes.Vendors]).toBe(ServiceSubscriptionTier.Partner);
|
|
59
|
+
expect(SERVICE_TIER_FROM_SERVICE_TYPE[ServiceTypes.Exhibitors]).toBe(
|
|
60
|
+
ServiceSubscriptionTier.Partner
|
|
61
|
+
);
|
|
62
|
+
expect(SERVICE_TIER_FROM_SERVICE_TYPE[ServiceTypes.Sponsors]).toBe(ServiceSubscriptionTier.Partner);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("maps venue-related types to Patron", () => {
|
|
66
|
+
expect(SERVICE_TIER_FROM_SERVICE_TYPE[ServiceTypes.Venues]).toBe(ServiceSubscriptionTier.Patron);
|
|
67
|
+
expect(SERVICE_TIER_FROM_SERVICE_TYPE[ServiceTypes.Organizations]).toBe(
|
|
68
|
+
ServiceSubscriptionTier.Patron
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("USER_SUBSCRIPTION_TYPES", () => {
|
|
74
|
+
test("Basic tier is free and typed as MembershipTier.Basic", () => {
|
|
75
|
+
expect(USER_SUBSCRIPTION_TYPES.Basic).toEqual({
|
|
76
|
+
name: "Basic",
|
|
77
|
+
description: "Basic",
|
|
78
|
+
price: 0,
|
|
79
|
+
type: MembershipTier.Basic,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("only Basic tier is currently defined", () => {
|
|
84
|
+
expect(Object.keys(USER_SUBSCRIPTION_TYPES)).toEqual(["Basic"]);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Gender } from "@prisma/client";
|
|
2
|
+
import { describe, expect, test } from "@jest/globals";
|
|
3
|
+
import { getDisplayGender, type GenderDisplayUser } from "../userUtils.js";
|
|
4
|
+
|
|
5
|
+
describe("getDisplayGender", () => {
|
|
6
|
+
test("returns trimmed genderIdentity when present", () => {
|
|
7
|
+
const user: GenderDisplayUser = {
|
|
8
|
+
gender: Gender.Male,
|
|
9
|
+
genderIdentity: " Non-binary ",
|
|
10
|
+
};
|
|
11
|
+
expect(getDisplayGender(user)).toBe("Non-binary");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("genderIdentity wins over gender enum", () => {
|
|
15
|
+
const user: GenderDisplayUser = {
|
|
16
|
+
gender: Gender.Female,
|
|
17
|
+
genderIdentity: "Genderfluid",
|
|
18
|
+
};
|
|
19
|
+
expect(getDisplayGender(user)).toBe("Genderfluid");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("returns gender enum when genderIdentity is absent", () => {
|
|
23
|
+
expect(getDisplayGender({ gender: Gender.Male })).toBe(Gender.Male);
|
|
24
|
+
expect(getDisplayGender({ gender: Gender.Female })).toBe(Gender.Female);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("returns null when genderIdentity is blank whitespace", () => {
|
|
28
|
+
expect(getDisplayGender({ genderIdentity: " ", gender: Gender.Male })).toBe(
|
|
29
|
+
Gender.Male
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("returns null when neither field is set", () => {
|
|
34
|
+
expect(getDisplayGender({})).toBeNull();
|
|
35
|
+
expect(getDisplayGender({ gender: null, genderIdentity: null })).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("returns null when genderIdentity is empty and gender is null", () => {
|
|
39
|
+
expect(getDisplayGender({ genderIdentity: "", gender: null })).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { formatVenueType, formatVenueTypes } from "../venueTypeFormatter.js";
|
|
3
|
+
|
|
4
|
+
describe("venueTypeFormatter", () => {
|
|
5
|
+
describe("formatVenueType", () => {
|
|
6
|
+
test("inserts spaces before capital letters", () => {
|
|
7
|
+
expect(formatVenueType("OutdoorSpace")).toBe("Outdoor Space");
|
|
8
|
+
expect(formatVenueType("WeddingVenue")).toBe("Wedding Venue");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("returns empty string for falsy input", () => {
|
|
12
|
+
expect(formatVenueType("")).toBe("");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("formatVenueTypes", () => {
|
|
17
|
+
test("formats up to maxDisplay types", () => {
|
|
18
|
+
expect(formatVenueTypes(["Restaurant", "Bar"], 2)).toBe("Restaurant, Bar");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("appends +N more when truncated", () => {
|
|
22
|
+
expect(formatVenueTypes(["Restaurant", "Bar", "Nightclub", "Rooftop"], 2)).toBe(
|
|
23
|
+
"Restaurant, Bar +2 more"
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("returns empty string for empty array", () => {
|
|
28
|
+
expect(formatVenueTypes([])).toBe("");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|