@koloseum/utils 0.1.3 → 0.1.4

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.
@@ -0,0 +1,139 @@
1
+ import type { Database } from "@koloseum/types/database";
2
+ import type { CustomError, SocialMediaPlatform } from "@koloseum/types/public/auth";
3
+ import type { PostgrestError, SupabaseClient } from "@supabase/supabase-js";
4
+ import type { MobilePhoneLocale } from "validator";
5
+ export declare const Status: {
6
+ /**
7
+ * A generic error message.
8
+ */
9
+ ERROR: string;
10
+ /**
11
+ * A generic loading message.
12
+ */
13
+ LOADING: string;
14
+ /**
15
+ * A generic password reset request message.
16
+ */
17
+ PASSWORD_RESET_REQUESTED: string;
18
+ };
19
+ export declare const Utility: {
20
+ /**
21
+ * Calls an Edge function.
22
+ * @param {SupabaseClient} supabase - The Supabase client
23
+ * @param {string} path - The path to the Edge function
24
+ * @param {"GET" | "POST"} [method="GET"] - The HTTP method to use
25
+ * @returns The response from the Edge function
26
+ */
27
+ callEdgeFunction: (supabase: SupabaseClient<Database>, path: string, method?: "GET" | "POST") => Promise<{
28
+ data?: any;
29
+ error?: string;
30
+ }>;
31
+ /**
32
+ * Capitalises a given word.
33
+ * @param {string} word - The word to be capitalised
34
+ * @returns The capitalised word
35
+ */
36
+ capitalise: (word: string) => string;
37
+ /**
38
+ * Returns a custom error object.
39
+ * @param {number} code - The error code; defaults to `500` if not provided or invalid
40
+ * @param {string} message - The error message
41
+ * @returns An object with the error `code` and `message`
42
+ */
43
+ customError: (code: number | undefined, message: string) => CustomError;
44
+ /**
45
+ * A regular expression for E.164 phone numbers.
46
+ *
47
+ * - Source: https://www.twilio.com/docs/glossary/what-e164
48
+ */
49
+ e164Regex: RegExp;
50
+ /**
51
+ * Formats a date in the format DD/MM/YYYY (by default) or YYYY-MM-DD (for client).
52
+ * @param {string} date - Date to be formatted
53
+ * @param {boolean} forClient - Specify whether the data is for displaying on the client; defaults to `false`
54
+ * @returns The formatted date string, or `null` if invalid input or in case of an error
55
+ */
56
+ formatDate: (date: string, forClient?: boolean) => string | null;
57
+ /**
58
+ * Formats a social media platform for display.
59
+ * @param {SocialMediaPlatform} platform - The social media platform to be formatted
60
+ * @returns The formatted social media platform string
61
+ */
62
+ formatSocialMediaPlatform: (platform: SocialMediaPlatform) => string;
63
+ /**
64
+ * Returns an age in years given a birth date.
65
+ * @param {string} dateOfBirth - The date of birth as a string, e.g. `DD-MM-YYYY`
66
+ * @returns The age in years, or `NaN` if the input is invalid
67
+ */
68
+ getAge: (dateOfBirth: string) => number;
69
+ /**
70
+ * A regular expression for Koloseum Lounge Branch IDs. It covers the following rules:
71
+ * - 9 characters long
72
+ * - begins with "KLB" followed by 7 digits
73
+ */
74
+ loungeBranchIdRegex: RegExp;
75
+ /**
76
+ * A regular expression for Koloseum Lounge IDs. It covers the following rules:
77
+ * - 9 characters long
78
+ * - begins with "KL" followed by 7 digits
79
+ */
80
+ loungeIdRegex: RegExp;
81
+ /**
82
+ * The minimum birth date (from today's date) for a user who is a minor, i.e. `YYYY-MM-DD`
83
+ */
84
+ minimumMinorBirthDate: string;
85
+ /**
86
+ * Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
87
+ * @param {CustomError} error - The error object
88
+ * @returns A new `Error` object if the error is unexpected, or a Svelte error otherwise
89
+ */
90
+ parseLoadError: (error: CustomError) => Error;
91
+ /**
92
+ * Parses a `PostgrestError` object and returns a custom error object if any has occurred.
93
+ * @param {PostgrestError | null} postgrestError - The `PostgrestError` object, or `null` if no error occurred
94
+ * @returns An object with an `error` if any has occurred
95
+ */
96
+ parsePostgrestError: (postgrestError: PostgrestError | null) => {
97
+ error?: CustomError;
98
+ };
99
+ /**
100
+ * A regular expression for user passwords to match during authentication. It covers the following rules:
101
+ * - at least 8 characters long
102
+ * - at least one digit
103
+ * - at least one lowercase letter
104
+ * - at least one uppercase letter
105
+ * - at least one symbol
106
+ */
107
+ passwordRegex: RegExp;
108
+ /**
109
+ * A regular expression for Koloseum Player IDs. It covers the following rules:
110
+ * - 9 characters long
111
+ * - begins with "KP" followed by 7 digits
112
+ */
113
+ playerIdRegex: RegExp;
114
+ /**
115
+ * Sanitises any potential HTML injected into user input.
116
+ * @param {string} input - The input to be sanitised
117
+ * @returns A sanitised string, or an empty string if the input is invalid
118
+ */
119
+ sanitiseHtml: (input: string) => string;
120
+ /**
121
+ * A regular expression for social media handles, without a leading slash or @ character.
122
+ *
123
+ * - Source: https://stackoverflow.com/a/74579651
124
+ */
125
+ socialMediaHandleRegex: RegExp;
126
+ /**
127
+ * Validates an E.164 phone number.
128
+ * @param {string} phoneNumber - The phone number to be validated
129
+ * @param {"any" | MobilePhoneLocale | MobilePhoneLocale[]} [locale="any"] - The locale(s) to validate the phone number against
130
+ * @returns `true` if the phone number is valid; `false` otherwise
131
+ */
132
+ validateE164: (phoneNumber: string, locale?: "any" | MobilePhoneLocale | MobilePhoneLocale[]) => boolean;
133
+ /**
134
+ * Validates a social media handle.
135
+ * @param {string} handle - The social media handle to be validated
136
+ * @returns `true` if the handle is valid; `false` otherwise
137
+ */
138
+ validateSocialMediaHandle: (handle: string) => boolean;
139
+ };
package/dist/utils.js ADDED
@@ -0,0 +1,250 @@
1
+ import { FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from "@supabase/supabase-js";
2
+ import { error as svelteError } from "@sveltejs/kit";
3
+ import parser from "any-date-parser";
4
+ import sanitize from "sanitize-html";
5
+ import validator from "validator";
6
+ /* Helper functions */
7
+ const { isMobilePhone, isURL } = validator;
8
+ /* Status messages */
9
+ export const Status = {
10
+ /**
11
+ * A generic error message.
12
+ */
13
+ ERROR: "An unexpected error occurred. Kindly try again.",
14
+ /**
15
+ * A generic loading message.
16
+ */
17
+ LOADING: "Please wait…",
18
+ /**
19
+ * A generic password reset request message.
20
+ */
21
+ PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly.",
22
+ };
23
+ /* Utility functions */
24
+ export const Utility = {
25
+ /**
26
+ * Calls an Edge function.
27
+ * @param {SupabaseClient} supabase - The Supabase client
28
+ * @param {string} path - The path to the Edge function
29
+ * @param {"GET" | "POST"} [method="GET"] - The HTTP method to use
30
+ * @returns The response from the Edge function
31
+ */
32
+ callEdgeFunction: async (supabase, path, method = "GET") => {
33
+ // Fetch response
34
+ const { data, error } = await supabase.functions.invoke(path, { method });
35
+ // Return error if any
36
+ if (error instanceof FunctionsHttpError)
37
+ return { error: await error.context.json() };
38
+ else if (error instanceof FunctionsRelayError)
39
+ return { error: error.message };
40
+ else if (error instanceof FunctionsFetchError)
41
+ return { error: error.message };
42
+ // Return response
43
+ return { data };
44
+ },
45
+ /**
46
+ * Capitalises a given word.
47
+ * @param {string} word - The word to be capitalised
48
+ * @returns The capitalised word
49
+ */
50
+ capitalise: (word) => word.charAt(0).toUpperCase() +
51
+ word
52
+ .slice(1)
53
+ .split("")
54
+ .map((c) => c.toLowerCase())
55
+ .join(""),
56
+ /**
57
+ * Returns a custom error object.
58
+ * @param {number} code - The error code; defaults to `500` if not provided or invalid
59
+ * @param {string} message - The error message
60
+ * @returns An object with the error `code` and `message`
61
+ */
62
+ customError: (code, message) => (!code || code < 400 || code > 599 ? { code: 500, message: Status.ERROR } : { code, message }),
63
+ /**
64
+ * A regular expression for E.164 phone numbers.
65
+ *
66
+ * - Source: https://www.twilio.com/docs/glossary/what-e164
67
+ */
68
+ e164Regex: /^\+?[1-9]\d{1,14}$/,
69
+ /**
70
+ * Formats a date in the format DD/MM/YYYY (by default) or YYYY-MM-DD (for client).
71
+ * @param {string} date - Date to be formatted
72
+ * @param {boolean} forClient - Specify whether the data is for displaying on the client; defaults to `false`
73
+ * @returns The formatted date string, or `null` if invalid input or in case of an error
74
+ */
75
+ formatDate: (date, forClient = false) => {
76
+ if (date === "")
77
+ return null;
78
+ try {
79
+ const formattedDate = new Intl.DateTimeFormat(forClient ? "fr-CA" : "en-KE", {
80
+ day: "2-digit",
81
+ month: "2-digit",
82
+ year: "numeric",
83
+ // @ts-ignore: .fromString method exists but is not typed in `any-date-parser` package
84
+ }).format(parser.fromString(date));
85
+ return formattedDate;
86
+ }
87
+ catch (error) {
88
+ return null;
89
+ }
90
+ },
91
+ /**
92
+ * Formats a social media platform for display.
93
+ * @param {SocialMediaPlatform} platform - The social media platform to be formatted
94
+ * @returns The formatted social media platform string
95
+ */
96
+ formatSocialMediaPlatform: (platform) => {
97
+ const platforms = ["facebook", "instagram", "kick", "tiktok", "twitch", "twitter", "youtube"];
98
+ if (!platforms.includes(platform))
99
+ return "";
100
+ if (platform === "tiktok")
101
+ return "TikTok";
102
+ if (platform === "twitter")
103
+ return "X (formerly Twitter)";
104
+ if (platform === "youtube")
105
+ return "YouTube";
106
+ return Utility.capitalise(platform);
107
+ },
108
+ /**
109
+ * Returns an age in years given a birth date.
110
+ * @param {string} dateOfBirth - The date of birth as a string, e.g. `DD-MM-YYYY`
111
+ * @returns The age in years, or `NaN` if the input is invalid
112
+ */
113
+ getAge: (dateOfBirth) => {
114
+ if (dateOfBirth === "" || typeof dateOfBirth !== "string")
115
+ return NaN;
116
+ dateOfBirth = Utility.formatDate(dateOfBirth, true);
117
+ const birthDate = new Date(dateOfBirth);
118
+ const currentDate = new Date();
119
+ let age = currentDate.getFullYear() - birthDate.getFullYear();
120
+ const monthDiff = currentDate.getMonth() - birthDate.getMonth();
121
+ return monthDiff < 0 || (monthDiff === 0 && currentDate.getDate() < birthDate.getDate()) ? --age : age;
122
+ },
123
+ /**
124
+ * A regular expression for Koloseum Lounge Branch IDs. It covers the following rules:
125
+ * - 9 characters long
126
+ * - begins with "KLB" followed by 7 digits
127
+ */
128
+ loungeBranchIdRegex: /^KLB\d{7}$/,
129
+ /**
130
+ * A regular expression for Koloseum Lounge IDs. It covers the following rules:
131
+ * - 9 characters long
132
+ * - begins with "KL" followed by 7 digits
133
+ */
134
+ loungeIdRegex: /^KL\d{7}$/,
135
+ /**
136
+ * The minimum birth date (from today's date) for a user who is a minor, i.e. `YYYY-MM-DD`
137
+ */
138
+ minimumMinorBirthDate: (() => {
139
+ const today = new Date();
140
+ const tomorrow = new Date();
141
+ tomorrow.setUTCDate(today.getDate() + 1);
142
+ const tomorrow18YearsAgo = tomorrow.getFullYear() - 18;
143
+ tomorrow.setFullYear(tomorrow18YearsAgo);
144
+ return tomorrow.toISOString().split("T")[0];
145
+ })(),
146
+ /**
147
+ * Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
148
+ * @param {CustomError} error - The error object
149
+ * @returns A new `Error` object if the error is unexpected, or a Svelte error otherwise
150
+ */
151
+ parseLoadError: (error) => (error.code === 500 ? new Error(error.message) : svelteError(error.code, { message: error.message })),
152
+ /**
153
+ * Parses a `PostgrestError` object and returns a custom error object if any has occurred.
154
+ * @param {PostgrestError | null} postgrestError - The `PostgrestError` object, or `null` if no error occurred
155
+ * @returns An object with an `error` if any has occurred
156
+ */
157
+ parsePostgrestError: (postgrestError) => {
158
+ // Return undefined if no error occurred
159
+ let error;
160
+ if (!postgrestError)
161
+ return { error };
162
+ // Return custom error if hint is a number between 400 and 599
163
+ const customErrorCode = Number(postgrestError.hint);
164
+ if (!isNaN(customErrorCode) && customErrorCode >= 400 && customErrorCode <= 599) {
165
+ error = Utility.customError(customErrorCode, postgrestError.message);
166
+ return { error };
167
+ }
168
+ // Map Postgrest error codes to custom error codes
169
+ const errorMap = [
170
+ { code: "08", status: 503 },
171
+ { code: "09", status: 500 },
172
+ { code: "0L", status: 403 },
173
+ { code: "0P", status: 403 },
174
+ { code: "23503", status: 409 },
175
+ { code: "23505", status: 409 },
176
+ { code: "25006", status: 405 },
177
+ { code: "25", status: 500 },
178
+ { code: "28", status: 403 },
179
+ { code: "2D", status: 500 },
180
+ { code: "38", status: 500 },
181
+ { code: "39", status: 500 },
182
+ { code: "3B", status: 500 },
183
+ { code: "40", status: 500 },
184
+ { code: "53400", status: 500 },
185
+ { code: "53", status: 503 },
186
+ { code: "54", status: 500 },
187
+ { code: "55", status: 500 },
188
+ { code: "57", status: 500 },
189
+ { code: "58", status: 500 },
190
+ { code: "F0", status: 500 },
191
+ { code: "HV", status: 500 },
192
+ { code: "P0001", status: 400 },
193
+ { code: "P0", status: 500 },
194
+ { code: "XX", status: 500 },
195
+ { code: "42883", status: 404 },
196
+ { code: "42P01", status: 404 },
197
+ { code: "42P17", status: 500 },
198
+ { code: "42501", status: 403 },
199
+ ];
200
+ // Return custom error if Postgrest error code is found
201
+ for (const { code, status } of errorMap)
202
+ if (postgrestError.code === code || postgrestError.code.startsWith(code)) {
203
+ error = Utility.customError(status, Status.ERROR);
204
+ return { error };
205
+ }
206
+ // Return generic error
207
+ error = Utility.customError(500, Status.ERROR);
208
+ return { error };
209
+ },
210
+ /**
211
+ * A regular expression for user passwords to match during authentication. It covers the following rules:
212
+ * - at least 8 characters long
213
+ * - at least one digit
214
+ * - at least one lowercase letter
215
+ * - at least one uppercase letter
216
+ * - at least one symbol
217
+ */
218
+ passwordRegex: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()-_+=|{}[\]:;'<>,.?/~]).{8,}$/,
219
+ /**
220
+ * A regular expression for Koloseum Player IDs. It covers the following rules:
221
+ * - 9 characters long
222
+ * - begins with "KP" followed by 7 digits
223
+ */
224
+ playerIdRegex: /^KP\d{7}$/,
225
+ /**
226
+ * Sanitises any potential HTML injected into user input.
227
+ * @param {string} input - The input to be sanitised
228
+ * @returns A sanitised string, or an empty string if the input is invalid
229
+ */
230
+ sanitiseHtml: (input) => (typeof input !== "string" ? "" : sanitize(input, { allowedTags: [], allowedAttributes: {} })),
231
+ /**
232
+ * A regular expression for social media handles, without a leading slash or @ character.
233
+ *
234
+ * - Source: https://stackoverflow.com/a/74579651
235
+ */
236
+ socialMediaHandleRegex: /^([A-Za-z0-9_.]{3,25})$/gm,
237
+ /**
238
+ * Validates an E.164 phone number.
239
+ * @param {string} phoneNumber - The phone number to be validated
240
+ * @param {"any" | MobilePhoneLocale | MobilePhoneLocale[]} [locale="any"] - The locale(s) to validate the phone number against
241
+ * @returns `true` if the phone number is valid; `false` otherwise
242
+ */
243
+ validateE164: (phoneNumber, locale = "en-KE") => isMobilePhone(phoneNumber, locale) && Boolean(phoneNumber.match(Utility.e164Regex)),
244
+ /**
245
+ * Validates a social media handle.
246
+ * @param {string} handle - The social media handle to be validated
247
+ * @returns `true` if the handle is valid; `false` otherwise
248
+ */
249
+ validateSocialMediaHandle: (handle) => !isURL(handle, { require_protocol: false }) && !handle.startsWith("@") && Boolean(handle.match(Utility.socialMediaHandleRegex)),
250
+ };
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@koloseum/utils",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "author": "Koloseum Technologies Limited",
5
+ "type": "module",
5
6
  "description": "Utility logic for use across Koloseum web apps (TypeScript)",
6
7
  "keywords": [
7
8
  "Koloseum"
@@ -14,14 +15,16 @@
14
15
  "type": "git",
15
16
  "url": "git+https://github.com/koloseum-technologies/npm-utils.git"
16
17
  },
17
- "main": "./src/utils.ts",
18
+ "main": "./dist/utils.js",
18
19
  "exports": {
19
- "./src": "./src/utils.ts"
20
+ "./src": "./dist/utils.js"
20
21
  },
21
22
  "files": [
22
- "./src/**/*.ts"
23
+ "./src/**/*.ts",
24
+ "./dist/**/*"
23
25
  ],
24
26
  "scripts": {
27
+ "build": "npx tsc",
25
28
  "test": "vitest --run"
26
29
  },
27
30
  "dependencies": {
package/src/utils.test.ts CHANGED
@@ -28,7 +28,7 @@ describe("Utility helper", () => {
28
28
  parsePostgrestError,
29
29
  sanitiseHtml,
30
30
  validateE164,
31
- validateSocialMediaHandle
31
+ validateSocialMediaHandle,
32
32
  } = Utility;
33
33
 
34
34
  // Test each helper
@@ -99,65 +99,65 @@ describe("Utility helper", () => {
99
99
  expect(parsePostgrestError(null)).toEqual({ error: undefined });
100
100
  expect(
101
101
  parsePostgrestError({
102
- code: "42501",
103
- name: "insufficient_privilege",
102
+ code: "42501",
103
+ name: "insufficient_privilege",
104
104
  message: 'Insufficient privileges: permission denied for relation "restricted_table"',
105
105
  details: 'User does not have the necessary permissions to access the relation "restricted_table".',
106
- hint: ""
106
+ hint: "",
107
107
  })
108
108
  ).toEqual({ error: { code: 403, message: Status.ERROR } });
109
109
  expect(
110
110
  parsePostgrestError({
111
- code: "42883",
112
- name: "undefined_function",
111
+ code: "42883",
112
+ name: "undefined_function",
113
113
  message: "Undefined function: function non_existent_function() does not exist",
114
114
  details: "Function non_existent_function() does not exist.",
115
- hint: "No function matches the given name and argument types. You might need to add explicit type casts."
115
+ hint: "No function matches the given name and argument types. You might need to add explicit type casts.",
116
116
  })
117
117
  ).toEqual({ error: { code: 404, message: Status.ERROR } });
118
118
  expect(
119
119
  parsePostgrestError({
120
- code: "25006",
121
- name: "read_only_sql_transaction",
120
+ code: "25006",
121
+ name: "read_only_sql_transaction",
122
122
  message: "Read only SQL transaction: cannot execute INSERT in a read-only transaction",
123
123
  details: "Cannot execute INSERT in a read-only transaction.",
124
- hint: ""
124
+ hint: "",
125
125
  })
126
126
  ).toEqual({ error: { code: 405, message: Status.ERROR } });
127
127
  expect(
128
128
  parsePostgrestError({
129
- code: "23505",
130
- name: "unique_violation",
129
+ code: "23505",
130
+ name: "unique_violation",
131
131
  message: 'Uniqueness violation: duplicate key value violates unique constraint "users_username_key"',
132
132
  details: "Key (username)=(john_doe) already exists.",
133
- hint: ""
133
+ hint: "",
134
134
  })
135
135
  ).toEqual({ error: { code: 409, message: Status.ERROR } });
136
136
  expect(
137
137
  parsePostgrestError({
138
- code: "53400",
139
- name: "configuration_file_error",
138
+ code: "53400",
139
+ name: "configuration_file_error",
140
140
  message: "invalid page header in block 0 of relation base/12345/67890",
141
141
  details: "The page header is corrupted. This may indicate hardware or file system issues.",
142
- hint: ""
142
+ hint: "",
143
143
  })
144
144
  ).toEqual({ error: { code: 500, message: Status.ERROR } });
145
145
  expect(
146
146
  parsePostgrestError({
147
- code: "08003",
148
- name: "connection_does_not_exist",
147
+ code: "08003",
148
+ name: "connection_does_not_exist",
149
149
  message: "connection does not exist",
150
150
  details: "The connection to the database was lost or never established.",
151
- hint: ""
151
+ hint: "",
152
152
  })
153
153
  ).toEqual({ error: { code: 503, message: Status.ERROR } });
154
154
  expect(
155
155
  parsePostgrestError({
156
- code: "P0001",
157
- name: "raise_exception",
156
+ code: "P0001",
157
+ name: "raise_exception",
158
158
  message: "I'm a teapot!",
159
159
  details: "Pretty simple",
160
- hint: "418"
160
+ hint: "418",
161
161
  })
162
162
  ).toEqual({ error: { code: 418, message: "I'm a teapot!" } });
163
163
  });
package/src/utils.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import type { Database } from "@koloseum/types/database";
2
2
  import type { CustomError, SocialMediaPlatform } from "@koloseum/types/public/auth";
3
3
 
4
- import type { Page, Locator } from "@playwright/test";
5
4
  import type { PostgrestError, SupabaseClient } from "@supabase/supabase-js";
6
5
  import type { MobilePhoneLocale } from "validator";
7
6
 
8
- import { expect } from "@playwright/test";
9
7
  import { FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from "@supabase/supabase-js";
10
8
  import { error as svelteError } from "@sveltejs/kit";
11
9
 
@@ -91,8 +89,8 @@ export const Utility = {
91
89
  day: "2-digit",
92
90
  month: "2-digit",
93
91
  year: "numeric",
94
- // @ts-ignore: .fromString method exists but is not typed in `any-date-parser` package
95
- }).format(parser.fromString(date));
92
+ // @ts-ignore: .fromString method exists but is not typed in `any-date-parser` package
93
+ }).format(parser.fromString(date));
96
94
 
97
95
  return formattedDate;
98
96
  } catch (error) {
@@ -264,32 +262,3 @@ export const Utility = {
264
262
  */
265
263
  validateSocialMediaHandle: (handle: string) => !isURL(handle, { require_protocol: false }) && !handle.startsWith("@") && Boolean(handle.match(Utility.socialMediaHandleRegex)),
266
264
  };
267
-
268
- /* Testing functions */
269
- export const Testing = {
270
- /**
271
- * Asserts the absence of a field on the page.
272
- * @param {Locator[]} fields - The fields to check for absence
273
- * @returns A promise that resolves when the check is complete
274
- */
275
- assertFieldAbsence: async (...fields: Locator[]) => {
276
- for (const field of fields) await expect(field).not.toBeVisible();
277
- },
278
- /**
279
- * Clicks a locator and waits for a response.
280
- * @param {Page} page - The page on which to click the locator
281
- * @param {Locator} locator - The locator to click
282
- * @param {string} endpoint - The endpoint to wait for
283
- * @returns A promise that resolves when the click and response are complete
284
- */
285
- clickAndWait: async (page: Page, locator: Locator, endpoint: string) => {
286
- // Ensure the locator is visible
287
- await expect(locator).toBeVisible();
288
-
289
- // Click with force option to ensure the click happens
290
- await locator.click({ force: true });
291
-
292
- // Wait for the API response
293
- await page.waitForResponse(endpoint);
294
- },
295
- };