Compare commits
5 Commits
main
...
fix/bookin
Author | SHA1 | Date |
---|---|---|
CarinaWolli | 554b85a600 | |
CarinaWolli | dae16fa6e9 | |
CarinaWolli | a5f906b9b6 | |
CarinaWolli | f54c5675d6 | |
CarinaWolli | 2882b5f7ca |
|
@ -0,0 +1,18 @@
|
||||||
|
name: Auto Comment Merge Conflicts
|
||||||
|
on: push
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-comment-merge-conflicts:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: codytseng/auto-comment-merge-conflicts@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
comment-body: "Hey there, there is a merge conflict, can you take a look?"
|
||||||
|
wait-ms: 3000
|
||||||
|
max-retries: 5
|
||||||
|
label-name: "🚨 merge conflict"
|
||||||
|
ignore-authors: dependabot,otherAuthor
|
|
@ -3,6 +3,7 @@ import { z } from "zod";
|
||||||
import { _DestinationCalendarModel as DestinationCalendar } from "@calcom/prisma/zod";
|
import { _DestinationCalendarModel as DestinationCalendar } from "@calcom/prisma/zod";
|
||||||
|
|
||||||
export const schemaDestinationCalendarBaseBodyParams = DestinationCalendar.pick({
|
export const schemaDestinationCalendarBaseBodyParams = DestinationCalendar.pick({
|
||||||
|
credentialId: true,
|
||||||
integration: true,
|
integration: true,
|
||||||
externalId: true,
|
externalId: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
|
@ -14,6 +15,7 @@ const schemaDestinationCalendarCreateParams = z
|
||||||
.object({
|
.object({
|
||||||
integration: z.string(),
|
integration: z.string(),
|
||||||
externalId: z.string(),
|
externalId: z.string(),
|
||||||
|
credentialId: z.number(),
|
||||||
eventTypeId: z.number().optional(),
|
eventTypeId: z.number().optional(),
|
||||||
bookingId: z.number().optional(),
|
bookingId: z.number().optional(),
|
||||||
userId: z.number().optional(),
|
userId: z.number().optional(),
|
||||||
|
@ -45,4 +47,5 @@ export const schemaDestinationCalendarReadPublic = DestinationCalendar.pick({
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
bookingId: true,
|
bookingId: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
|
credentialId: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import type { Prisma } from "@prisma/client";
|
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
import type { z } from "zod";
|
|
||||||
|
|
||||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import type { PrismaClient } from "@calcom/prisma";
|
|
||||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
schemaDestinationCalendarEditBodyParams,
|
schemaDestinationCalendarEditBodyParams,
|
||||||
|
@ -62,251 +56,16 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
||||||
* 404:
|
* 404:
|
||||||
* description: Destination calendar not found
|
* description: Destination calendar not found
|
||||||
*/
|
*/
|
||||||
type DestinationCalendarType = {
|
|
||||||
userId?: number | null;
|
|
||||||
eventTypeId?: number | null;
|
|
||||||
credentialId: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UserCredentialType = {
|
|
||||||
id: number;
|
|
||||||
appId: string | null;
|
|
||||||
type: string;
|
|
||||||
userId: number | null;
|
|
||||||
user: {
|
|
||||||
email: string;
|
|
||||||
} | null;
|
|
||||||
teamId: number | null;
|
|
||||||
key: Prisma.JsonValue;
|
|
||||||
invalid: boolean | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function patchHandler(req: NextApiRequest) {
|
export async function patchHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin, prisma, query, body } = req;
|
const { prisma, query, body } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(query);
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
const parsedBody = schemaDestinationCalendarEditBodyParams.parse(body);
|
const parsedBody = schemaDestinationCalendarEditBodyParams.parse(body);
|
||||||
const assignedUserId = isAdmin ? parsedBody.userId || userId : userId;
|
|
||||||
|
|
||||||
validateIntegrationInput(parsedBody);
|
|
||||||
const destinationCalendarObject: DestinationCalendarType = await getDestinationCalendar(id, prisma);
|
|
||||||
await validateRequestAndOwnership({ destinationCalendarObject, parsedBody, assignedUserId, prisma });
|
|
||||||
|
|
||||||
const userCredentials = await getUserCredentials({
|
|
||||||
credentialId: destinationCalendarObject.credentialId,
|
|
||||||
userId: assignedUserId,
|
|
||||||
prisma,
|
|
||||||
});
|
|
||||||
const credentialId = await verifyCredentialsAndGetId({
|
|
||||||
parsedBody,
|
|
||||||
userCredentials,
|
|
||||||
currentCredentialId: destinationCalendarObject.credentialId,
|
|
||||||
});
|
|
||||||
// If the user has passed eventTypeId, we need to remove userId from the update data to make sure we don't link it to user as well
|
|
||||||
if (parsedBody.eventTypeId) parsedBody.userId = undefined;
|
|
||||||
const destinationCalendar = await prisma.destinationCalendar.update({
|
const destinationCalendar = await prisma.destinationCalendar.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { ...parsedBody, credentialId },
|
data: parsedBody,
|
||||||
});
|
});
|
||||||
return { destinationCalendar: schemaDestinationCalendarReadPublic.parse(destinationCalendar) };
|
return { destinationCalendar: schemaDestinationCalendarReadPublic.parse(destinationCalendar) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves user credentials associated with a given credential ID and user ID and validates if the credentials belong to this user
|
|
||||||
*
|
|
||||||
* @param credentialId - The ID of the credential to fetch. If not provided, an error is thrown.
|
|
||||||
* @param userId - The user ID against which the credentials need to be verified.
|
|
||||||
* @param prisma - An instance of PrismaClient for database operations.
|
|
||||||
*
|
|
||||||
* @returns - An array containing the matching user credentials.
|
|
||||||
*
|
|
||||||
* @throws HttpError - If `credentialId` is not provided or no associated credentials are found in the database.
|
|
||||||
*/
|
|
||||||
async function getUserCredentials({
|
|
||||||
credentialId,
|
|
||||||
userId,
|
|
||||||
prisma,
|
|
||||||
}: {
|
|
||||||
credentialId: number | null;
|
|
||||||
userId: number;
|
|
||||||
prisma: PrismaClient;
|
|
||||||
}) {
|
|
||||||
if (!credentialId) {
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 404,
|
|
||||||
message: `Destination calendar missing credential id`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const userCredentials = await prisma.credential.findMany({
|
|
||||||
where: { id: credentialId, userId },
|
|
||||||
select: credentialForCalendarServiceSelect,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!userCredentials || userCredentials.length === 0) {
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 400,
|
|
||||||
message: `Bad request, no associated credentials found`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return userCredentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies the provided credentials and retrieves the associated credential ID.
|
|
||||||
*
|
|
||||||
* This function checks if the `integration` and `externalId` properties from the parsed body are present.
|
|
||||||
* If both properties exist, it fetches the connected calendar credentials using the provided user credentials
|
|
||||||
* and checks for a matching external ID and integration from the list of connected calendars.
|
|
||||||
*
|
|
||||||
* If a match is found, it updates the `credentialId` with the one from the connected calendar.
|
|
||||||
* Otherwise, it throws an HTTP error with a 400 status indicating an invalid credential ID.
|
|
||||||
*
|
|
||||||
* If the parsed body does not contain the necessary properties, the function
|
|
||||||
* returns the `credentialId` from the destination calendar object.
|
|
||||||
*
|
|
||||||
* @param parsedBody - The parsed body from the incoming request, validated against a predefined schema.
|
|
||||||
* Checked if it contain properties like `integration` and `externalId`.
|
|
||||||
* @param userCredentials - An array of user credentials used to fetch the connected calendar credentials.
|
|
||||||
* @param destinationCalendarObject - An object representing the destination calendar. Primarily used
|
|
||||||
* to fetch the default `credentialId`.
|
|
||||||
*
|
|
||||||
* @returns - The verified `credentialId` either from the matched connected calendar in case of updating the destination calendar,
|
|
||||||
* or the provided destination calendar object in other cases.
|
|
||||||
*
|
|
||||||
* @throws HttpError - If no matching connected calendar is found for the given `integration` and `externalId`.
|
|
||||||
*/
|
|
||||||
async function verifyCredentialsAndGetId({
|
|
||||||
parsedBody,
|
|
||||||
userCredentials,
|
|
||||||
currentCredentialId,
|
|
||||||
}: {
|
|
||||||
parsedBody: z.infer<typeof schemaDestinationCalendarEditBodyParams>;
|
|
||||||
userCredentials: UserCredentialType[];
|
|
||||||
currentCredentialId: number | null;
|
|
||||||
}) {
|
|
||||||
if (parsedBody.integration && parsedBody.externalId) {
|
|
||||||
const calendarCredentials = getCalendarCredentials(userCredentials);
|
|
||||||
|
|
||||||
const { connectedCalendars } = await getConnectedCalendars(
|
|
||||||
calendarCredentials,
|
|
||||||
[],
|
|
||||||
parsedBody.externalId
|
|
||||||
);
|
|
||||||
const eligibleCalendars = connectedCalendars[0]?.calendars?.filter((calendar) => !calendar.readOnly);
|
|
||||||
const calendar = eligibleCalendars?.find(
|
|
||||||
(c) => c.externalId === parsedBody.externalId && c.integration === parsedBody.integration
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!calendar?.credentialId)
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 400,
|
|
||||||
message: "Bad request, credential id invalid",
|
|
||||||
});
|
|
||||||
return calendar?.credentialId;
|
|
||||||
}
|
|
||||||
return currentCredentialId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the request for updating a destination calendar.
|
|
||||||
*
|
|
||||||
* This function checks the validity of the provided eventTypeId against the existing destination calendar object
|
|
||||||
* in the sense that if the destination calendar is not linked to an event type, the eventTypeId can not be provided.
|
|
||||||
*
|
|
||||||
* It also ensures that the eventTypeId, if provided, belongs to the assigned user.
|
|
||||||
*
|
|
||||||
* @param destinationCalendarObject - An object representing the destination calendar.
|
|
||||||
* @param parsedBody - The parsed body from the incoming request, validated against a predefined schema.
|
|
||||||
* @param assignedUserId - The user ID assigned for the operation, which might be an admin or a regular user.
|
|
||||||
* @param prisma - An instance of PrismaClient for database operations.
|
|
||||||
*
|
|
||||||
* @throws HttpError - If the validation fails or inconsistencies are detected in the request data.
|
|
||||||
*/
|
|
||||||
async function validateRequestAndOwnership({
|
|
||||||
destinationCalendarObject,
|
|
||||||
parsedBody,
|
|
||||||
assignedUserId,
|
|
||||||
prisma,
|
|
||||||
}: {
|
|
||||||
destinationCalendarObject: DestinationCalendarType;
|
|
||||||
parsedBody: z.infer<typeof schemaDestinationCalendarEditBodyParams>;
|
|
||||||
assignedUserId: number;
|
|
||||||
prisma: PrismaClient;
|
|
||||||
}) {
|
|
||||||
if (parsedBody.eventTypeId) {
|
|
||||||
if (!destinationCalendarObject.eventTypeId) {
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 400,
|
|
||||||
message: `The provided destination calendar can not be linked to an event type`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const userEventType = await prisma.eventType.findFirst({
|
|
||||||
where: { id: parsedBody.eventTypeId },
|
|
||||||
select: { userId: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!userEventType || userEventType.userId !== assignedUserId) {
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 404,
|
|
||||||
message: `Event type with ID ${parsedBody.eventTypeId} not found`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parsedBody.eventTypeId) {
|
|
||||||
if (destinationCalendarObject.eventTypeId) {
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 400,
|
|
||||||
message: `The provided destination calendar can only be linked to an event type`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (destinationCalendarObject.userId !== assignedUserId) {
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 403,
|
|
||||||
message: `Forbidden`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the destination calendar based on the provided ID as the path parameter, specifically `credentialId` and `eventTypeId`.
|
|
||||||
*
|
|
||||||
* If no matching destination calendar is found for the provided ID, an HTTP error with a 404 status
|
|
||||||
* indicating that the desired destination calendar was not found is thrown.
|
|
||||||
*
|
|
||||||
* @param id - The ID of the destination calendar to be retrieved.
|
|
||||||
* @param prisma - An instance of PrismaClient for database operations.
|
|
||||||
*
|
|
||||||
* @returns - An object containing details of the matching destination calendar, specifically `credentialId` and `eventTypeId`.
|
|
||||||
*
|
|
||||||
* @throws HttpError - If no destination calendar matches the provided ID.
|
|
||||||
*/
|
|
||||||
async function getDestinationCalendar(id: number, prisma: PrismaClient) {
|
|
||||||
const destinationCalendarObject = await prisma.destinationCalendar.findFirst({
|
|
||||||
where: {
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
select: { userId: true, eventTypeId: true, credentialId: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!destinationCalendarObject) {
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 404,
|
|
||||||
message: `Destination calendar with ID ${id} not found`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return destinationCalendarObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateIntegrationInput(parsedBody: z.infer<typeof schemaDestinationCalendarEditBodyParams>) {
|
|
||||||
if (parsedBody.integration && !parsedBody.externalId) {
|
|
||||||
throw new HttpError({ statusCode: 400, message: "External Id is required with integration value" });
|
|
||||||
}
|
|
||||||
if (!parsedBody.integration && parsedBody.externalId) {
|
|
||||||
throw new HttpError({ statusCode: 400, message: "Integration value is required with external ID" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defaultResponder(patchHandler);
|
export default defaultResponder(patchHandler);
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
schemaDestinationCalendarReadPublic,
|
schemaDestinationCalendarReadPublic,
|
||||||
|
@ -40,6 +38,9 @@ import {
|
||||||
* externalId:
|
* externalId:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 'The external ID of the integration'
|
* description: 'The external ID of the integration'
|
||||||
|
* credentialId:
|
||||||
|
* type: integer
|
||||||
|
* description: 'The credential ID it is associated with'
|
||||||
* eventTypeId:
|
* eventTypeId:
|
||||||
* type: integer
|
* type: integer
|
||||||
* description: 'The ID of the eventType it is associated with'
|
* description: 'The ID of the eventType it is associated with'
|
||||||
|
@ -64,38 +65,20 @@ async function postHandler(req: NextApiRequest) {
|
||||||
const parsedBody = schemaDestinationCalendarCreateBodyParams.parse(body);
|
const parsedBody = schemaDestinationCalendarCreateBodyParams.parse(body);
|
||||||
await checkPermissions(req, userId);
|
await checkPermissions(req, userId);
|
||||||
|
|
||||||
const assignedUserId = isAdmin && parsedBody.userId ? parsedBody.userId : userId;
|
const assignedUserId = isAdmin ? parsedBody.userId || userId : userId;
|
||||||
|
|
||||||
/* Check if credentialId data matches the ownership and integration passed in */
|
/* Check if credentialId data matches the ownership and integration passed in */
|
||||||
const userCredentials = await prisma.credential.findMany({
|
const credential = await prisma.credential.findFirst({
|
||||||
where: {
|
where: { type: parsedBody.integration, userId: assignedUserId },
|
||||||
type: parsedBody.integration,
|
select: { id: true, type: true, userId: true },
|
||||||
userId: assignedUserId,
|
|
||||||
},
|
|
||||||
select: credentialForCalendarServiceSelect,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userCredentials.length === 0)
|
if (!credential)
|
||||||
throw new HttpError({
|
throw new HttpError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
message: "Bad request, credential id invalid",
|
message: "Bad request, credential id invalid",
|
||||||
});
|
});
|
||||||
|
|
||||||
const calendarCredentials = getCalendarCredentials(userCredentials);
|
|
||||||
|
|
||||||
const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, [], parsedBody.externalId);
|
|
||||||
|
|
||||||
const eligibleCalendars = connectedCalendars[0]?.calendars?.filter((calendar) => !calendar.readOnly);
|
|
||||||
const calendar = eligibleCalendars?.find(
|
|
||||||
(c) => c.externalId === parsedBody.externalId && c.integration === parsedBody.integration
|
|
||||||
);
|
|
||||||
if (!calendar?.credentialId)
|
|
||||||
throw new HttpError({
|
|
||||||
statusCode: 400,
|
|
||||||
message: "Bad request, credential id invalid",
|
|
||||||
});
|
|
||||||
const credentialId = calendar.credentialId;
|
|
||||||
|
|
||||||
if (parsedBody.eventTypeId) {
|
if (parsedBody.eventTypeId) {
|
||||||
const eventType = await prisma.eventType.findFirst({
|
const eventType = await prisma.eventType.findFirst({
|
||||||
where: { id: parsedBody.eventTypeId, userId: parsedBody.userId },
|
where: { id: parsedBody.eventTypeId, userId: parsedBody.userId },
|
||||||
|
@ -108,9 +91,7 @@ async function postHandler(req: NextApiRequest) {
|
||||||
parsedBody.userId = undefined;
|
parsedBody.userId = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const destination_calendar = await prisma.destinationCalendar.create({
|
const destination_calendar = await prisma.destinationCalendar.create({ data: { ...parsedBody } });
|
||||||
data: { ...parsedBody, credentialId },
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
destinationCalendar: schemaDestinationCalendarReadPublic.parse(destination_calendar),
|
destinationCalendar: schemaDestinationCalendarReadPublic.parse(destination_calendar),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "3.4.6",
|
"version": "3.4.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
|
|
|
@ -1,441 +0,0 @@
|
||||||
import { loginUser } from "../fixtures/regularBookings";
|
|
||||||
import { test } from "../lib/fixtures";
|
|
||||||
|
|
||||||
test.describe("Booking With Phone Question and Each Other Question", () => {
|
|
||||||
const bookingOptions = { hasPlaceholder: true, isRequired: true };
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page, users, bookingPage }) => {
|
|
||||||
await loginUser(users);
|
|
||||||
await page.goto("/event-types");
|
|
||||||
await bookingPage.goToEventType("30 min");
|
|
||||||
await bookingPage.goToTab("event_advanced_tab_title");
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and Address Question", () => {
|
|
||||||
test("Select required and Address required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("address", "address-test", "address test", true, "address test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and Address question (both required)",
|
|
||||||
secondQuestion: "address",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and Address not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("address", "address-test", "address test", false, "address test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and Address question (only select required)",
|
|
||||||
secondQuestion: "address",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and checkbox group Question", () => {
|
|
||||||
test("Select required and checkbox group required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("checkbox", "checkbox-test", "checkbox test", true);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and Checkbox question (both required)",
|
|
||||||
secondQuestion: "checkbox",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and checkbox group not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("checkbox", "checkbox-test", "checkbox test", false);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and Checkbox question (only Select required)",
|
|
||||||
secondQuestion: "checkbox",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and checkbox Question", () => {
|
|
||||||
test("Select required and checkbox required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("boolean", "boolean-test", "boolean test", true);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and boolean question (both required)",
|
|
||||||
secondQuestion: "boolean",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and checkbox not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("boolean", "boolean-test", "boolean test", false);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and boolean question (only select required)",
|
|
||||||
secondQuestion: "boolean",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and Long text Question", () => {
|
|
||||||
test("Select required and Long text required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("textarea", "textarea-test", "textarea test", true, "textarea test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and textarea question (both required)",
|
|
||||||
secondQuestion: "textarea",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and Long text not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("textarea", "textarea-test", "textarea test", false, "textarea test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and textarea question (only select required)",
|
|
||||||
secondQuestion: "textarea",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and Multi email Question", () => {
|
|
||||||
test("Select required and Multi email required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion(
|
|
||||||
"multiemail",
|
|
||||||
"multiemail-test",
|
|
||||||
"multiemail test",
|
|
||||||
true,
|
|
||||||
"multiemail test"
|
|
||||||
);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and multiemail question (both required)",
|
|
||||||
secondQuestion: "multiemail",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and Multi email not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion(
|
|
||||||
"multiemail",
|
|
||||||
"multiemail-test",
|
|
||||||
"multiemail test",
|
|
||||||
false,
|
|
||||||
"multiemail test"
|
|
||||||
);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and multiemail question (only select required)",
|
|
||||||
secondQuestion: "multiemail",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and multiselect Question", () => {
|
|
||||||
test("Select required and multiselect text required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("multiselect", "multiselect-test", "multiselect test", true);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and multiselect question (both required)",
|
|
||||||
secondQuestion: "multiselect",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and multiselect text not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("multiselect", "multiselect-test", "multiselect test", false);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and multiselect question (only select required)",
|
|
||||||
secondQuestion: "multiselect",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and Number Question", () => {
|
|
||||||
test("Select required and Number required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("number", "number-test", "number test", true, "number test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and number question (both required)",
|
|
||||||
secondQuestion: "number",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and Number not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("number", "number-test", "number test", false, "number test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and number question (only select required)",
|
|
||||||
secondQuestion: "number",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and Phone Question", () => {
|
|
||||||
test("Select required and select required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("phone", "phone-test", "phone test", true, "phone test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and phone question (both required)",
|
|
||||||
secondQuestion: "phone",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and Phone not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("phone", "phone-test", "phone test", false, "phone test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and phone question (only select required)",
|
|
||||||
secondQuestion: "phone",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and Radio group Question", () => {
|
|
||||||
test("Select required and Radio group required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("radio", "radio-test", "radio test", true);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and radio question (both required)",
|
|
||||||
secondQuestion: "radio",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and Radio group not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("radio", "radio-test", "radio test", false);
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and radio question (only select required)",
|
|
||||||
secondQuestion: "radio",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Booking With Select Question and Short text question", () => {
|
|
||||||
test("Select required and Short text required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("text", "text-test", "text test", true, "text test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and text question (both required)",
|
|
||||||
secondQuestion: "text",
|
|
||||||
options: bookingOptions,
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Select and Short text not required", async ({ bookingPage }) => {
|
|
||||||
await bookingPage.addQuestion("select", "select-test", "select test", true, "select test");
|
|
||||||
await bookingPage.addQuestion("text", "text-test", "text test", false, "text test");
|
|
||||||
await bookingPage.updateEventType();
|
|
||||||
const eventTypePage = await bookingPage.previewEventType();
|
|
||||||
await bookingPage.selectTimeSlot(eventTypePage);
|
|
||||||
await bookingPage.fillAndConfirmBooking({
|
|
||||||
eventTypePage,
|
|
||||||
placeholderText: "Please share anything that will help prepare for our meeting.",
|
|
||||||
question: "select",
|
|
||||||
fillText: "Test Select question and text question (only select required)",
|
|
||||||
secondQuestion: "text",
|
|
||||||
options: { ...bookingOptions, isRequired: false },
|
|
||||||
});
|
|
||||||
await bookingPage.rescheduleBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingRescheduled(eventTypePage);
|
|
||||||
await bookingPage.cancelBooking(eventTypePage);
|
|
||||||
await bookingPage.assertBookingCanceled(eventTypePage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -111,6 +111,7 @@ const fillQuestion = async (eventTypePage: Page, questionType: string, customLoc
|
||||||
await eventTypePage.getByPlaceholder(`${questionType} test`).fill("text test");
|
await eventTypePage.getByPlaceholder(`${questionType} test`).fill("text test");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (questionActions[questionType]) {
|
if (questionActions[questionType]) {
|
||||||
await questionActions[questionType]();
|
await questionActions[questionType]();
|
||||||
}
|
}
|
||||||
|
@ -235,7 +236,7 @@ export function createBookingPageFixture(page: Page) {
|
||||||
|
|
||||||
// Change the selector for specifics cases related to select question
|
// Change the selector for specifics cases related to select question
|
||||||
const shouldChangeSelectLocator = (question: string, secondQuestion: string): boolean =>
|
const shouldChangeSelectLocator = (question: string, secondQuestion: string): boolean =>
|
||||||
question === "select" && ["multiemail", "multiselect", "address"].includes(secondQuestion);
|
question === "select" && ["multiemail", "multiselect"].includes(secondQuestion);
|
||||||
|
|
||||||
const shouldUseLastRadioGroupLocator = (question: string, secondQuestion: string): boolean =>
|
const shouldUseLastRadioGroupLocator = (question: string, secondQuestion: string): boolean =>
|
||||||
question === "radio" && secondQuestion === "checkbox";
|
question === "radio" && secondQuestion === "checkbox";
|
||||||
|
|
|
@ -4,6 +4,7 @@ import prismock from "../../../../tests/libs/__mocks__/prisma";
|
||||||
import { diff } from "jest-diff";
|
import { diff } from "jest-diff";
|
||||||
import { describe, expect, vi, beforeEach, afterEach, test } from "vitest";
|
import { describe, expect, vi, beforeEach, afterEach, test } from "vitest";
|
||||||
|
|
||||||
|
import dayjs from "@calcom/dayjs";
|
||||||
import type { BookingStatus } from "@calcom/prisma/enums";
|
import type { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
|
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
|
||||||
import { getAvailableSlots as getSchedule } from "@calcom/trpc/server/routers/viewer/slots/util";
|
import { getAvailableSlots as getSchedule } from "@calcom/trpc/server/routers/viewer/slots/util";
|
||||||
|
@ -874,6 +875,128 @@ describe("getSchedule", () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
test("test that booking limit is working correctly if user is all day available", async () => {
|
||||||
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||||
|
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
||||||
|
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
|
||||||
|
|
||||||
|
const scenarioData = {
|
||||||
|
eventTypes: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
length: 60,
|
||||||
|
beforeEventBuffer: 0,
|
||||||
|
afterEventBuffer: 0,
|
||||||
|
bookingLimits: {
|
||||||
|
PER_DAY: 1,
|
||||||
|
},
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
length: 60,
|
||||||
|
beforeEventBuffer: 0,
|
||||||
|
afterEventBuffer: 0,
|
||||||
|
bookingLimits: {
|
||||||
|
PER_DAY: 2,
|
||||||
|
},
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
...TestData.users.example,
|
||||||
|
id: 101,
|
||||||
|
schedules: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "All Day available",
|
||||||
|
availability: [
|
||||||
|
{
|
||||||
|
userId: null,
|
||||||
|
eventTypeId: null,
|
||||||
|
days: [0, 1, 2, 3, 4, 5, 6],
|
||||||
|
startTime: new Date("1970-01-01T00:00:00.000Z"),
|
||||||
|
endTime: new Date("1970-01-01T23:59:59.999Z"),
|
||||||
|
date: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timeZone: Timezones["+5:30"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bookings: [
|
||||||
|
{
|
||||||
|
userId: 101,
|
||||||
|
eventTypeId: 1,
|
||||||
|
startTime: `${plus2DateString}T08:30:00.000Z`,
|
||||||
|
endTime: `${plus2DateString}T08:29:59.999Z`,
|
||||||
|
status: "ACCEPTED" as BookingStatus,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: 101,
|
||||||
|
eventTypeId: 2,
|
||||||
|
startTime: `${plus2DateString}T08:30:00.000Z`,
|
||||||
|
endTime: `${plus2DateString}T08:29:59.999Z`,
|
||||||
|
status: "ACCEPTED" as BookingStatus,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await createBookingScenario(scenarioData);
|
||||||
|
|
||||||
|
const thisUserAvailabilityBookingLimitOne = await getSchedule({
|
||||||
|
input: {
|
||||||
|
eventTypeId: 1,
|
||||||
|
eventTypeSlug: "",
|
||||||
|
startTime: `${plus1DateString}T00:00:00.000Z`,
|
||||||
|
endTime: `${plus3DateString}T23:59:59.999Z`,
|
||||||
|
timeZone: Timezones["+5:30"],
|
||||||
|
isTeamEvent: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const thisUserAvailabilityBookingLimitTwo = await getSchedule({
|
||||||
|
input: {
|
||||||
|
eventTypeId: 2,
|
||||||
|
eventTypeSlug: "",
|
||||||
|
startTime: `${plus1DateString}T00:00:00.000Z`,
|
||||||
|
endTime: `${plus3DateString}T23:59:59.999Z`,
|
||||||
|
timeZone: Timezones["+5:30"],
|
||||||
|
isTeamEvent: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let countSlotsOnDayWithBooking = 0;
|
||||||
|
for (const date in thisUserAvailabilityBookingLimitOne.slots) {
|
||||||
|
for (const timeObj of thisUserAvailabilityBookingLimitOne.slots[date]) {
|
||||||
|
if (dayjs(timeObj.time).tz(Timezones["+5:30"]).format().startsWith(plus2DateString)) {
|
||||||
|
countSlotsOnDayWithBooking++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(countSlotsOnDayWithBooking).toBe(0); // 1 booking per day as limit
|
||||||
|
|
||||||
|
countSlotsOnDayWithBooking = 0;
|
||||||
|
for (const date in thisUserAvailabilityBookingLimitTwo.slots) {
|
||||||
|
for (const timeObj of thisUserAvailabilityBookingLimitTwo.slots[date]) {
|
||||||
|
if (dayjs(timeObj.time).tz(Timezones["+5:30"]).format().startsWith(plus2DateString)) {
|
||||||
|
countSlotsOnDayWithBooking++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(countSlotsOnDayWithBooking).toBe(23); // 2 booking per day as limit
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Team Event", () => {
|
describe("Team Event", () => {
|
||||||
|
|
|
@ -101,6 +101,9 @@ export type InputEventType = {
|
||||||
requiresConfirmation?: boolean;
|
requiresConfirmation?: boolean;
|
||||||
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
|
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
|
||||||
schedule?: InputUser["schedules"][number];
|
schedule?: InputUser["schedules"][number];
|
||||||
|
bookingLimits?: {
|
||||||
|
PER_DAY?: number;
|
||||||
|
};
|
||||||
} & Partial<Omit<Prisma.EventTypeCreateInput, "users" | "schedule">>;
|
} & Partial<Omit<Prisma.EventTypeCreateInput, "users" | "schedule">>;
|
||||||
|
|
||||||
type WhiteListedBookingProps = {
|
type WhiteListedBookingProps = {
|
||||||
|
@ -199,6 +202,7 @@ async function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser
|
||||||
timeZone: null,
|
timeZone: null,
|
||||||
beforeEventBuffer: 0,
|
beforeEventBuffer: 0,
|
||||||
afterEventBuffer: 0,
|
afterEventBuffer: 0,
|
||||||
|
bookingLimits: {},
|
||||||
schedulingType: null,
|
schedulingType: null,
|
||||||
length: 15,
|
length: 15,
|
||||||
//TODO: What is the purpose of periodStartDate and periodEndDate? Test these?
|
//TODO: What is the purpose of periodStartDate and periodEndDate? Test these?
|
||||||
|
|
|
@ -161,8 +161,8 @@ const EmailStep = (props: { translationString: string; iconsrc: string }) => {
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "#E5E7EB",
|
backgroundColor: "#E5E7EB",
|
||||||
borderRadius: "48px",
|
borderRadius: "48px",
|
||||||
minHeight: "48px",
|
height: "48px",
|
||||||
minWidth: "48px",
|
width: "48px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|
|
@ -205,7 +205,11 @@ export const stringOrNumber = z.union([
|
||||||
z.number().int(),
|
z.number().int(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const stringToDayjs = z.string().transform((val) => dayjs(val));
|
export const stringToDayjs = z.string().transform((val) => {
|
||||||
|
const matches = val.match(/([+-]\d{2}:\d{2})$/)[1];
|
||||||
|
const timezone = matches ? matches[1] : "+00:00";
|
||||||
|
return dayjs(val).utcOffset(timezone);
|
||||||
|
});
|
||||||
|
|
||||||
export const bookingCreateBodySchema = z.object({
|
export const bookingCreateBodySchema = z.object({
|
||||||
end: z.string().optional(),
|
end: z.string().optional(),
|
||||||
|
|
Loading…
Reference in New Issue