@bash-app/bash-common 30.247.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.
Files changed (172) hide show
  1. package/README.md +2 -0
  2. package/dist/__tests__/definitionsHelpers.test.d.ts +2 -0
  3. package/dist/__tests__/definitionsHelpers.test.d.ts.map +1 -0
  4. package/dist/__tests__/definitionsHelpers.test.js +85 -0
  5. package/dist/__tests__/definitionsHelpers.test.js.map +1 -0
  6. package/dist/__tests__/extendedSchemas.test.d.ts +2 -0
  7. package/dist/__tests__/extendedSchemas.test.d.ts.map +1 -0
  8. package/dist/__tests__/extendedSchemas.test.js +40 -0
  9. package/dist/__tests__/extendedSchemas.test.js.map +1 -0
  10. package/dist/__tests__/membershipDefinitions.test.d.ts +2 -0
  11. package/dist/__tests__/membershipDefinitions.test.d.ts.map +1 -0
  12. package/dist/__tests__/membershipDefinitions.test.js +95 -0
  13. package/dist/__tests__/membershipDefinitions.test.js.map +1 -0
  14. package/dist/__tests__/ticketAdmission.test.d.ts +5 -0
  15. package/dist/__tests__/ticketAdmission.test.d.ts.map +1 -0
  16. package/dist/__tests__/ticketAdmission.test.js +42 -0
  17. package/dist/__tests__/ticketAdmission.test.js.map +1 -0
  18. package/dist/__tests__/utmAttribution.test.d.ts +2 -0
  19. package/dist/__tests__/utmAttribution.test.d.ts.map +1 -0
  20. package/dist/__tests__/utmAttribution.test.js +92 -0
  21. package/dist/__tests__/utmAttribution.test.js.map +1 -0
  22. package/dist/extendedSchemas.d.ts +2 -0
  23. package/dist/extendedSchemas.d.ts.map +1 -1
  24. package/dist/extendedSchemas.js +2 -0
  25. package/dist/extendedSchemas.js.map +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/sms/smsTemplates.d.ts +27 -0
  31. package/dist/sms/smsTemplates.d.ts.map +1 -0
  32. package/dist/sms/smsTemplates.js +11 -0
  33. package/dist/sms/smsTemplates.js.map +1 -0
  34. package/dist/utils/__tests__/addressUtils.test.d.ts +2 -0
  35. package/dist/utils/__tests__/addressUtils.test.d.ts.map +1 -0
  36. package/dist/utils/__tests__/addressUtils.test.js +138 -0
  37. package/dist/utils/__tests__/addressUtils.test.js.map +1 -0
  38. package/dist/utils/__tests__/apiUtils.test.d.ts +5 -0
  39. package/dist/utils/__tests__/apiUtils.test.d.ts.map +1 -0
  40. package/dist/utils/__tests__/apiUtils.test.js +161 -0
  41. package/dist/utils/__tests__/apiUtils.test.js.map +1 -0
  42. package/dist/utils/__tests__/arrayUtils.test.d.ts +2 -0
  43. package/dist/utils/__tests__/arrayUtils.test.d.ts.map +1 -0
  44. package/dist/utils/__tests__/arrayUtils.test.js +23 -0
  45. package/dist/utils/__tests__/arrayUtils.test.js.map +1 -0
  46. package/dist/utils/__tests__/blogUtils.test.d.ts +2 -0
  47. package/dist/utils/__tests__/blogUtils.test.d.ts.map +1 -0
  48. package/dist/utils/__tests__/blogUtils.test.js +102 -0
  49. package/dist/utils/__tests__/blogUtils.test.js.map +1 -0
  50. package/dist/utils/__tests__/dateTimeUtils.test.d.ts +2 -0
  51. package/dist/utils/__tests__/dateTimeUtils.test.d.ts.map +1 -0
  52. package/dist/utils/__tests__/dateTimeUtils.test.js +180 -0
  53. package/dist/utils/__tests__/dateTimeUtils.test.js.map +1 -0
  54. package/dist/utils/__tests__/discountEngine/discountCalculator.test.d.ts +2 -0
  55. package/dist/utils/__tests__/discountEngine/discountCalculator.test.d.ts.map +1 -0
  56. package/dist/utils/__tests__/discountEngine/discountCalculator.test.js +356 -0
  57. package/dist/utils/__tests__/discountEngine/discountCalculator.test.js.map +1 -0
  58. package/dist/utils/__tests__/generalDateTimeUtils.test.d.ts +2 -0
  59. package/dist/utils/__tests__/generalDateTimeUtils.test.d.ts.map +1 -0
  60. package/dist/utils/__tests__/generalDateTimeUtils.test.js +26 -0
  61. package/dist/utils/__tests__/generalDateTimeUtils.test.js.map +1 -0
  62. package/dist/utils/__tests__/luxonUtils.test.d.ts +2 -0
  63. package/dist/utils/__tests__/luxonUtils.test.d.ts.map +1 -0
  64. package/dist/utils/__tests__/luxonUtils.test.js +139 -0
  65. package/dist/utils/__tests__/luxonUtils.test.js.map +1 -0
  66. package/dist/utils/__tests__/mathUtils.test.d.ts +5 -0
  67. package/dist/utils/__tests__/mathUtils.test.d.ts.map +1 -0
  68. package/dist/utils/__tests__/mathUtils.test.js +26 -0
  69. package/dist/utils/__tests__/mathUtils.test.js.map +1 -0
  70. package/dist/utils/__tests__/objUtils.test.d.ts +5 -0
  71. package/dist/utils/__tests__/objUtils.test.d.ts.map +1 -0
  72. package/dist/utils/__tests__/objUtils.test.js +41 -0
  73. package/dist/utils/__tests__/objUtils.test.js.map +1 -0
  74. package/dist/utils/__tests__/paymentUtils.test.js +48 -1
  75. package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
  76. package/dist/utils/__tests__/qrCodeUtils.test.js +16 -1
  77. package/dist/utils/__tests__/qrCodeUtils.test.js.map +1 -1
  78. package/dist/utils/__tests__/reviewUtils.test.d.ts +2 -0
  79. package/dist/utils/__tests__/reviewUtils.test.d.ts.map +1 -0
  80. package/dist/utils/__tests__/reviewUtils.test.js +49 -0
  81. package/dist/utils/__tests__/reviewUtils.test.js.map +1 -0
  82. package/dist/utils/__tests__/slugUtils.test.d.ts +2 -0
  83. package/dist/utils/__tests__/slugUtils.test.d.ts.map +1 -0
  84. package/dist/utils/__tests__/slugUtils.test.js +103 -0
  85. package/dist/utils/__tests__/slugUtils.test.js.map +1 -0
  86. package/dist/utils/__tests__/sortUtils.test.d.ts +5 -0
  87. package/dist/utils/__tests__/sortUtils.test.d.ts.map +1 -0
  88. package/dist/utils/__tests__/sortUtils.test.js +28 -0
  89. package/dist/utils/__tests__/sortUtils.test.js.map +1 -0
  90. package/dist/utils/__tests__/stringUtils.test.d.ts +2 -0
  91. package/dist/utils/__tests__/stringUtils.test.d.ts.map +1 -0
  92. package/dist/utils/__tests__/stringUtils.test.js +27 -0
  93. package/dist/utils/__tests__/stringUtils.test.js.map +1 -0
  94. package/dist/utils/__tests__/ticketListUtils.test.d.ts +2 -0
  95. package/dist/utils/__tests__/ticketListUtils.test.d.ts.map +1 -0
  96. package/dist/utils/__tests__/ticketListUtils.test.js +41 -0
  97. package/dist/utils/__tests__/ticketListUtils.test.js.map +1 -0
  98. package/dist/utils/__tests__/typeUtils.test.d.ts +2 -0
  99. package/dist/utils/__tests__/typeUtils.test.d.ts.map +1 -0
  100. package/dist/utils/__tests__/typeUtils.test.js +11 -0
  101. package/dist/utils/__tests__/typeUtils.test.js.map +1 -0
  102. package/dist/utils/__tests__/userSubscriptionUtils.test.d.ts +5 -0
  103. package/dist/utils/__tests__/userSubscriptionUtils.test.d.ts.map +1 -0
  104. package/dist/utils/__tests__/userSubscriptionUtils.test.js +66 -0
  105. package/dist/utils/__tests__/userSubscriptionUtils.test.js.map +1 -0
  106. package/dist/utils/__tests__/userUtils.test.d.ts +2 -0
  107. package/dist/utils/__tests__/userUtils.test.d.ts.map +1 -0
  108. package/dist/utils/__tests__/userUtils.test.js +34 -0
  109. package/dist/utils/__tests__/userUtils.test.js.map +1 -0
  110. package/dist/utils/__tests__/venueTypeFormatter.test.d.ts +2 -0
  111. package/dist/utils/__tests__/venueTypeFormatter.test.d.ts.map +1 -0
  112. package/dist/utils/__tests__/venueTypeFormatter.test.js +25 -0
  113. package/dist/utils/__tests__/venueTypeFormatter.test.js.map +1 -0
  114. package/dist/utils/service/__tests__/regexUtils.test.d.ts +2 -0
  115. package/dist/utils/service/__tests__/regexUtils.test.d.ts.map +1 -0
  116. package/dist/utils/service/__tests__/regexUtils.test.js +51 -0
  117. package/dist/utils/service/__tests__/regexUtils.test.js.map +1 -0
  118. package/dist/utils/service/__tests__/serviceDBUtils.test.d.ts +2 -0
  119. package/dist/utils/service/__tests__/serviceDBUtils.test.d.ts.map +1 -0
  120. package/dist/utils/service/__tests__/serviceDBUtils.test.js +17 -0
  121. package/dist/utils/service/__tests__/serviceDBUtils.test.js.map +1 -0
  122. package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.d.ts +2 -0
  123. package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.d.ts.map +1 -0
  124. package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.js +60 -0
  125. package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.js.map +1 -0
  126. package/dist/utils/service/__tests__/serviceUtils.test.d.ts +2 -0
  127. package/dist/utils/service/__tests__/serviceUtils.test.d.ts.map +1 -0
  128. package/dist/utils/service/__tests__/serviceUtils.test.js +135 -0
  129. package/dist/utils/service/__tests__/serviceUtils.test.js.map +1 -0
  130. package/dist/utils/service/serviceFeaturedSortUtils.d.ts.map +1 -1
  131. package/dist/utils/service/serviceFeaturedSortUtils.js +5 -1
  132. package/dist/utils/service/serviceFeaturedSortUtils.js.map +1 -1
  133. package/dist/utils/ticketListUtils.d.ts.map +1 -1
  134. package/dist/utils/ticketListUtils.js +11 -7
  135. package/dist/utils/ticketListUtils.js.map +1 -1
  136. package/package.json +1 -1
  137. package/prisma/schema.prisma +47 -0
  138. package/src/__tests__/definitionsHelpers.test.ts +136 -0
  139. package/src/__tests__/extendedSchemas.test.ts +50 -0
  140. package/src/__tests__/membershipDefinitions.test.ts +144 -0
  141. package/src/__tests__/ticketAdmission.test.ts +73 -0
  142. package/src/__tests__/utmAttribution.test.ts +118 -0
  143. package/src/extendedSchemas.ts +2 -0
  144. package/src/index.ts +1 -0
  145. package/src/sms/smsTemplates.ts +20 -0
  146. package/src/utils/__tests__/addressUtils.test.ts +176 -0
  147. package/src/utils/__tests__/apiUtils.test.ts +219 -0
  148. package/src/utils/__tests__/arrayUtils.test.ts +26 -0
  149. package/src/utils/__tests__/blogUtils.test.ts +145 -0
  150. package/src/utils/__tests__/dateTimeUtils.test.ts +233 -0
  151. package/src/utils/__tests__/discountEngine/discountCalculator.test.ts +410 -0
  152. package/src/utils/__tests__/generalDateTimeUtils.test.ts +34 -0
  153. package/src/utils/__tests__/luxonUtils.test.ts +171 -0
  154. package/src/utils/__tests__/mathUtils.test.ts +30 -0
  155. package/src/utils/__tests__/objUtils.test.ts +48 -0
  156. package/src/utils/__tests__/paymentUtils.test.ts +59 -0
  157. package/src/utils/__tests__/qrCodeUtils.test.ts +28 -1
  158. package/src/utils/__tests__/reviewUtils.test.ts +65 -0
  159. package/src/utils/__tests__/slugUtils.test.ts +143 -0
  160. package/src/utils/__tests__/sortUtils.test.ts +33 -0
  161. package/src/utils/__tests__/stringUtils.test.ts +37 -0
  162. package/src/utils/__tests__/ticketListUtils.test.ts +47 -0
  163. package/src/utils/__tests__/typeUtils.test.ts +12 -0
  164. package/src/utils/__tests__/userSubscriptionUtils.test.ts +87 -0
  165. package/src/utils/__tests__/userUtils.test.ts +41 -0
  166. package/src/utils/__tests__/venueTypeFormatter.test.ts +31 -0
  167. package/src/utils/service/__tests__/regexUtils.test.ts +66 -0
  168. package/src/utils/service/__tests__/serviceDBUtils.test.ts +19 -0
  169. package/src/utils/service/__tests__/serviceFeaturedSortUtils.test.ts +67 -0
  170. package/src/utils/service/__tests__/serviceUtils.test.ts +190 -0
  171. package/src/utils/service/serviceFeaturedSortUtils.ts +5 -1
  172. package/src/utils/ticketListUtils.ts +13 -6
@@ -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
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import {
3
+ isPositiveInt,
4
+ isPositiveFloat,
5
+ isPrice,
6
+ isUrlInputtingProtocol,
7
+ isUrl,
8
+ } from "../regexUtils.js";
9
+
10
+ describe("regexUtils", () => {
11
+ describe("isPositiveInt", () => {
12
+ test("accepts digits and empty string", () => {
13
+ expect(isPositiveInt("123")).toBe(true);
14
+ expect(isPositiveInt("")).toBe(true);
15
+ });
16
+
17
+ test("rejects non-digit characters", () => {
18
+ expect(isPositiveInt("12a")).toBe(false);
19
+ expect(isPositiveInt("-1")).toBe(false);
20
+ });
21
+ });
22
+
23
+ describe("isPositiveFloat", () => {
24
+ test("accepts decimal numbers", () => {
25
+ expect(isPositiveFloat("12.5")).toBe(true);
26
+ expect(isPositiveFloat("0.")).toBe(true);
27
+ });
28
+
29
+ test("rejects letters", () => {
30
+ expect(isPositiveFloat("1.2.3")).toBe(false);
31
+ });
32
+ });
33
+
34
+ describe("isPrice", () => {
35
+ test("allows up to two decimal places", () => {
36
+ expect(isPrice("19.99")).toBe(true);
37
+ expect(isPrice("100")).toBe(true);
38
+ });
39
+
40
+ test("rejects more than two decimal places", () => {
41
+ expect(isPrice("1.999")).toBe(false);
42
+ });
43
+ });
44
+
45
+ describe("isUrlInputtingProtocol", () => {
46
+ test("matches partial protocol typing", () => {
47
+ expect(isUrlInputtingProtocol("https://")).toBe(true);
48
+ expect(isUrlInputtingProtocol("htt")).toBe(true);
49
+ });
50
+
51
+ test("rejects random text", () => {
52
+ expect(isUrlInputtingProtocol("example.com")).toBe(false);
53
+ });
54
+ });
55
+
56
+ describe("isUrl", () => {
57
+ test("accepts http(s) URLs and bare domains", () => {
58
+ expect(isUrl("https://bash.events")).toBe(true);
59
+ expect(isUrl("bash.events")).toBe(true);
60
+ });
61
+
62
+ test("rejects strings with spaces", () => {
63
+ expect(isUrl("not a url")).toBe(false);
64
+ });
65
+ });
66
+ });
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import { serviceFromDb, serviceToDb } from "../serviceDBUtils.js";
3
+
4
+ describe("serviceDBUtils", () => {
5
+ test("serviceFromDb returns the same reference (identity transform)", () => {
6
+ const service = { id: "svc-1", serviceName: "Test" } as any;
7
+ expect(serviceFromDb(service)).toBe(service);
8
+ });
9
+
10
+ test("serviceFromDb passes through null and undefined", () => {
11
+ expect(serviceFromDb(null)).toBeNull();
12
+ expect(serviceFromDb(undefined)).toBeUndefined();
13
+ });
14
+
15
+ test("serviceToDb returns the same partial payload", () => {
16
+ const partial = { tagline: "Hello" };
17
+ expect(serviceToDb(partial)).toBe(partial);
18
+ });
19
+ });
@@ -0,0 +1,67 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import {
3
+ compareFeaturedServices,
4
+ MIN_FEATURED_SERVICES_FOR_SECTION,
5
+ } from "../serviceFeaturedSortUtils.js";
6
+
7
+ describe("serviceFeaturedSortUtils", () => {
8
+ const now = new Date("2024-06-15T12:00:00Z");
9
+ const cmp = compareFeaturedServices(now);
10
+
11
+ test("ranks paid features above auto-featured and unfeatured", () => {
12
+ const paid = {
13
+ isFeaturedService: true,
14
+ featuredServiceUntil: new Date("2025-01-01"),
15
+ featuredAt: new Date("2024-01-01"),
16
+ featuredReason: "Paid" as const,
17
+ createdAt: new Date("2023-01-01"),
18
+ };
19
+ const auto = {
20
+ isFeaturedService: true,
21
+ featuredServiceUntil: new Date("2025-01-01"),
22
+ featuredAt: new Date("2024-02-01"),
23
+ featuredReason: "AutoNewService" as const,
24
+ createdAt: new Date("2024-05-01"),
25
+ };
26
+ const plain = {
27
+ isFeaturedService: false,
28
+ createdAt: new Date("2024-06-01"),
29
+ };
30
+ expect(cmp(paid, auto)).toBeLessThan(0);
31
+ expect(cmp(auto, plain)).toBeLessThan(0);
32
+ });
33
+
34
+ test("sorts paid tier FIFO by featuredAt ascending", () => {
35
+ const earlier = {
36
+ isFeaturedService: true,
37
+ featuredServiceUntil: new Date("2025-01-01"),
38
+ featuredAt: new Date("2024-01-01"),
39
+ featuredReason: "Admin" as const,
40
+ createdAt: new Date("2023-01-01"),
41
+ };
42
+ const later = {
43
+ ...earlier,
44
+ featuredAt: new Date("2024-03-01"),
45
+ };
46
+ expect(cmp(earlier, later)).toBeLessThan(0);
47
+ });
48
+
49
+ test("treats expired featuredUntil as unfeatured tier", () => {
50
+ const expired = {
51
+ isFeaturedService: true,
52
+ featuredServiceUntil: new Date("2024-01-01"),
53
+ featuredAt: new Date("2023-01-01"),
54
+ featuredReason: "Paid" as const,
55
+ createdAt: new Date("2024-06-01"),
56
+ };
57
+ const active = {
58
+ isFeaturedService: false,
59
+ createdAt: new Date("2024-05-01"),
60
+ };
61
+ expect(cmp(expired, active)).toBeGreaterThan(0);
62
+ });
63
+
64
+ test("exports homepage carousel floor constant", () => {
65
+ expect(MIN_FEATURED_SERVICES_FOR_SECTION).toBe(3);
66
+ });
67
+ });
@@ -0,0 +1,190 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import { ServiceSubscriptionStatus, ServiceTypes } from "@prisma/client";
3
+ import {
4
+ serviceSubscriptionIsActive,
5
+ serviceGetSpecificType,
6
+ serviceDetailUrl,
7
+ serviceWizardUrl,
8
+ partnerServiceProfileWizardPaymentMethodUrl,
9
+ venueWizardLoyaltyBuilderUrl,
10
+ serviceCheckoutUrl,
11
+ serviceTypeToField,
12
+ aiServiceInfoToInfoString,
13
+ aiServiceInfoToExampleString,
14
+ buildAIFieldsString,
15
+ serviceRelevantData,
16
+ serviceExcludeIds,
17
+ serviceExcludeOwners,
18
+ serviceGetInfoAI,
19
+ getVendorSalesLabel,
20
+ SERVICE_CANCELLATION_POLICY_DATA,
21
+ PARTNER_SERVICE_PROFILE_WIZARD_PAYMENT_METHOD_MAJOR_STEP,
22
+ VENUE_WIZARD_LOYALTY_MAJOR_STEP,
23
+ } from "../serviceUtils.js";
24
+
25
+ describe("serviceUtils", () => {
26
+ describe("serviceSubscriptionIsActive", () => {
27
+ test("returns true for Normal and Trialing", () => {
28
+ expect(serviceSubscriptionIsActive(ServiceSubscriptionStatus.Normal)).toBe(true);
29
+ expect(serviceSubscriptionIsActive(ServiceSubscriptionStatus.Trialing)).toBe(true);
30
+ });
31
+
32
+ test("returns false for canceled statuses", () => {
33
+ expect(serviceSubscriptionIsActive(ServiceSubscriptionStatus.None)).toBe(false);
34
+ });
35
+ });
36
+
37
+ describe("serviceGetSpecificType", () => {
38
+ test("maps venue service to venueId", () => {
39
+ expect(
40
+ serviceGetSpecificType({
41
+ id: "svc-1",
42
+ serviceType: ServiceTypes.Venues,
43
+ venueId: "venue-99",
44
+ } as any)
45
+ ).toEqual({
46
+ serviceId: "svc-1",
47
+ serviceType: ServiceTypes.Venues,
48
+ specificFieldName: "venue",
49
+ specificId: "venue-99",
50
+ });
51
+ });
52
+
53
+ test("returns null when serviceType is missing", () => {
54
+ expect(serviceGetSpecificType({ id: "svc-1" } as any)).toBeNull();
55
+ });
56
+ });
57
+
58
+ describe("URL builders", () => {
59
+ test("serviceDetailUrl", () => {
60
+ expect(serviceDetailUrl("s1", "Venues", "v1")).toBe("/services/s1/Venues/v1");
61
+ });
62
+
63
+ test("serviceWizardUrl includes step indices", () => {
64
+ expect(serviceWizardUrl("s1", "Vendors", "v1", 2, 1)).toBe(
65
+ "/wz/services/s1/Vendors/v1/2/1"
66
+ );
67
+ });
68
+
69
+ test("partnerServiceProfileWizardPaymentMethodUrl uses payment step constants", () => {
70
+ expect(partnerServiceProfileWizardPaymentMethodUrl("s1", "Vendors", "v1")).toBe(
71
+ `/wz/services/s1/Vendors/v1/${PARTNER_SERVICE_PROFILE_WIZARD_PAYMENT_METHOD_MAJOR_STEP}/3`
72
+ );
73
+ });
74
+
75
+ test("venueWizardLoyaltyBuilderUrl uses loyalty major step", () => {
76
+ expect(venueWizardLoyaltyBuilderUrl("s1", "Venues", "v1")).toBe(
77
+ `/wz/services/s1/Venues/v1/${VENUE_WIZARD_LOYALTY_MAJOR_STEP}/0`
78
+ );
79
+ });
80
+
81
+ test("serviceCheckoutUrl", () => {
82
+ expect(serviceCheckoutUrl("s1", "Venues", "v1")).toBe("/checkout/services/s1/Venues/v1");
83
+ });
84
+ });
85
+
86
+ describe("serviceTypeToField", () => {
87
+ test("maps enum to specific relation key", () => {
88
+ expect(serviceTypeToField(ServiceTypes.Vendors)).toBe("vendor");
89
+ expect(serviceTypeToField(ServiceTypes.EntertainmentServices)).toBe("entertainmentService");
90
+ });
91
+ });
92
+
93
+ describe("AI string helpers", () => {
94
+ const info = {
95
+ description: "Venue name",
96
+ example: "The Loft",
97
+ type: "string" as const,
98
+ isArray: false,
99
+ };
100
+
101
+ test("aiServiceInfoToInfoString", () => {
102
+ expect(aiServiceInfoToInfoString("serviceName", info)).toBe(
103
+ "serviceName: Venue name - string"
104
+ );
105
+ });
106
+
107
+ test("aiServiceInfoToExampleString marks arrays", () => {
108
+ expect(
109
+ aiServiceInfoToExampleString("tags", { ...info, isArray: true, example: '["A"]' })
110
+ ).toBe('tags: ["A"] - string[]');
111
+ });
112
+
113
+ test("buildAIFieldsString formats values and missing keys", () => {
114
+ const out = buildAIFieldsString(
115
+ { capacity: 150, vibe: null },
116
+ {
117
+ capacity: { description: "cap", example: "100", type: "integer", isArray: false },
118
+ vibe: { description: "vibe", example: "Chill", type: "string", isArray: false },
119
+ }
120
+ );
121
+ expect(out).toContain("capacity: 150");
122
+ expect(out).toContain("vibe: No vibe provided");
123
+ });
124
+ });
125
+
126
+ describe("serviceRelevantData", () => {
127
+ test("strips ids, owners, rates, dates, and misc fields", () => {
128
+ const result = serviceRelevantData({
129
+ id: "svc-1",
130
+ ownerId: "u1",
131
+ creatorId: "u2",
132
+ serviceName: "Hidden",
133
+ serviceType: ServiceTypes.Venues,
134
+ createdAt: new Date(),
135
+ owner: { id: "u1" },
136
+ tagline: "Keep me",
137
+ } as any);
138
+ expect(result).toEqual({ tagline: "Keep me" });
139
+ });
140
+ });
141
+
142
+ describe("serviceExclude helpers", () => {
143
+ test("serviceExcludeIds removes foreign keys and ids", () => {
144
+ const result = serviceExcludeIds({
145
+ id: "svc-1",
146
+ venueId: "v1",
147
+ tagline: "Stay",
148
+ } as any);
149
+ expect(result).toEqual({ tagline: "Stay" });
150
+ });
151
+
152
+ test("serviceExcludeOwners removes nested owner/creator", () => {
153
+ const result = serviceExcludeOwners({
154
+ owner: { id: "u1" },
155
+ creator: { id: "u2" },
156
+ description: "Keep",
157
+ } as any);
158
+ expect(result).toEqual({ description: "Keep" });
159
+ });
160
+ });
161
+
162
+ describe("serviceGetInfoAI", () => {
163
+ test("returns field metadata for venue services", () => {
164
+ const info = serviceGetInfoAI(ServiceTypes.Venues);
165
+ expect(info).toBeDefined();
166
+ expect(Object.keys(info).length).toBeGreaterThan(0);
167
+ });
168
+ });
169
+
170
+ describe("getVendorSalesLabel", () => {
171
+ test("returns Products for vendors and Merch for others", () => {
172
+ expect(getVendorSalesLabel(ServiceTypes.Vendors)).toBe("Products");
173
+ expect(getVendorSalesLabel(ServiceTypes.EntertainmentServices)).toBe("Merch");
174
+ });
175
+ });
176
+
177
+ describe("SERVICE_CANCELLATION_POLICY_DATA", () => {
178
+ test("generates guest-facing copy for Flexible policy", () => {
179
+ const policy = SERVICE_CANCELLATION_POLICY_DATA.Flexible;
180
+ expect(policy.description).toContain("Guests may cancel");
181
+ expect(policy.refundPolicy).toHaveLength(2);
182
+ });
183
+
184
+ test("generates vendor copy for VendorFlexible", () => {
185
+ const policy = SERVICE_CANCELLATION_POLICY_DATA.VendorFlexible;
186
+ expect(policy.description).toContain("the vendor");
187
+ expect(policy.description).toContain("verified booking");
188
+ });
189
+ });
190
+ });
@@ -39,7 +39,11 @@ function isActiveFeatured(s: FeaturedServiceSortable, now: Date): boolean {
39
39
  * and are treated as "legacy featured" sitting just below auto-features).
40
40
  */
41
41
  function featuredTier(s: FeaturedServiceSortable, now: Date): number {
42
- if (!isActiveFeatured(s, now)) return 3;
42
+ if (!isActiveFeatured(s, now)) {
43
+ // Expired paid feature still has isFeaturedService set; rank below never-featured.
44
+ if (s.isFeaturedService) return 4;
45
+ return 3;
46
+ }
43
47
  if (s.featuredReason && PAID_TIER_REASONS.has(s.featuredReason)) return 0;
44
48
  if (s.featuredReason === "AutoNewService") return 1;
45
49
  return 2;
@@ -110,14 +110,21 @@ export function ticketListStrToTicketList(
110
110
  ): Map<string, NumberOfTicketsForDate[]> {
111
111
  const ticketList: Map<string, NumberOfTicketsForDate[]> = new Map();
112
112
 
113
- // Decode the Base64 URL-safe string first
113
+ // Legacy plain strings use tier delimiters; encoded payloads are base64-only.
114
114
  let decodedStr: string;
115
- try {
116
- decodedStr = decodeBase64UrlSafe(ticketListStr);
117
- } catch (error) {
118
- console.error("Failed to decode ticketListStr:", error);
119
- // If decoding fails, try using the string as-is (for backward compatibility)
115
+ if (
116
+ ticketListStr.includes(
117
+ URL_PARAMS_TICKET_TIER_ID_NUMBER_OF_TICKETS_DATE_DELIM
118
+ )
119
+ ) {
120
120
  decodedStr = ticketListStr;
121
+ } else {
122
+ try {
123
+ decodedStr = decodeBase64UrlSafe(ticketListStr);
124
+ } catch (error) {
125
+ console.error("Failed to decode ticketListStr:", error);
126
+ decodedStr = ticketListStr;
127
+ }
121
128
  }
122
129
 
123
130
  // [ticketTierId]__~~[numberOfTickets];;[date]~~[numberOfTickets];;[date]~~...,...