525 lines
18 KiB
TypeScript
525 lines
18 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
import { expect } from "@playwright/test";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
|
|
import dayjs from "@calcom/dayjs";
|
|
import { randomString } from "@calcom/lib/random";
|
|
import prisma from "@calcom/prisma";
|
|
|
|
import { test } from "./lib/fixtures";
|
|
import { submitAndWaitForResponse, localize } from "./lib/testUtils";
|
|
|
|
test.describe.configure({ mode: "parallel" });
|
|
test.afterEach(async ({ users }) => {
|
|
await users.deleteAll();
|
|
});
|
|
|
|
test.describe("Out of office", () => {
|
|
test("User can create out of office entry", async ({ page, users }) => {
|
|
const user = await users.create({ name: "userOne" });
|
|
|
|
await user.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
const reasonListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeReasonList?batch=1") && response.status() === 200
|
|
);
|
|
await page.getByTestId("add_entry_ooo").click();
|
|
await reasonListRespPromise;
|
|
|
|
await page.getByTestId("reason_select").click();
|
|
|
|
await page.getByTestId("select-option-4").click();
|
|
|
|
await page.getByTestId("notes_input").click();
|
|
await page.getByTestId("notes_input").fill("Demo notes");
|
|
|
|
// send request
|
|
await saveAndWaitForResponse(page);
|
|
|
|
await expect(page.locator(`data-testid=table-redirect-n-a`)).toBeVisible();
|
|
});
|
|
|
|
test("User can configure booking redirect", async ({ page, users }) => {
|
|
const user = await users.create({ name: "userOne" });
|
|
const userTo = await users.create({ name: "userTwo" });
|
|
|
|
const team = await prisma.team.create({
|
|
data: {
|
|
name: "test-insights",
|
|
slug: `test-insights-${Date.now()}-${randomString(5)}}`,
|
|
},
|
|
});
|
|
|
|
// create memberships
|
|
await prisma.membership.createMany({
|
|
data: [
|
|
{
|
|
userId: user.id,
|
|
teamId: team.id,
|
|
accepted: true,
|
|
role: "ADMIN",
|
|
},
|
|
{
|
|
userId: userTo.id,
|
|
teamId: team.id,
|
|
accepted: true,
|
|
role: "ADMIN",
|
|
},
|
|
],
|
|
});
|
|
|
|
await user.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
const reasonListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeReasonList?batch=1") && response.status() === 200
|
|
);
|
|
await page.getByTestId("add_entry_ooo").click();
|
|
await reasonListRespPromise;
|
|
|
|
await page.getByTestId("reason_select").click();
|
|
|
|
await page.getByTestId("select-option-4").click();
|
|
|
|
await page.getByTestId("notes_input").click();
|
|
await page.getByTestId("notes_input").fill("Demo notes");
|
|
|
|
await page.getByTestId("profile-redirect-switch").click();
|
|
|
|
await page.getByTestId(`team_username_select_${userTo.id}`).click();
|
|
|
|
// send request
|
|
await saveAndWaitForResponse(page);
|
|
|
|
// expect table-redirect-toUserId to be visible
|
|
await expect(page.locator(`data-testid=table-redirect-${userTo.username}`)).toBeVisible();
|
|
});
|
|
|
|
test("User can edit out of office entry", async ({ page, users }) => {
|
|
const user = await users.create({ name: "userOne" });
|
|
const userTo = await users.create({ name: "userTwo" });
|
|
const userToSecond = await users.create({ name: "userThree" });
|
|
|
|
const team = await prisma.team.create({
|
|
data: {
|
|
name: "test-insights",
|
|
slug: `test-insights-${Date.now()}-${randomString(5)}}`,
|
|
},
|
|
});
|
|
|
|
// create memberships
|
|
await prisma.membership.createMany({
|
|
data: [
|
|
{
|
|
userId: user.id,
|
|
teamId: team.id,
|
|
accepted: true,
|
|
role: "ADMIN",
|
|
},
|
|
{
|
|
userId: userTo.id,
|
|
teamId: team.id,
|
|
accepted: true,
|
|
role: "ADMIN",
|
|
},
|
|
{
|
|
userId: userToSecond.id,
|
|
teamId: team.id,
|
|
accepted: true,
|
|
role: "ADMIN",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Skip creating the ooo entry through front-end as we can assume that it has already been tested above.
|
|
const uuid = uuidv4();
|
|
await prisma.outOfOfficeEntry.create({
|
|
data: {
|
|
start: dayjs().startOf("day").toDate(),
|
|
end: dayjs().startOf("day").add(1, "w").toDate(),
|
|
uuid,
|
|
user: { connect: { id: user.id } },
|
|
toUser: { connect: { id: userTo.id } },
|
|
createdAt: new Date(),
|
|
reason: {
|
|
connect: {
|
|
id: 1,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
await user.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
// expect table-redirect-toUserId to be visible
|
|
await expect(page.locator(`data-testid=table-redirect-${userTo.username}`)).toBeVisible();
|
|
|
|
// Open the edit modal and change redirect user and note.
|
|
await page.getByTestId(`ooo-edit-${userTo.username}`).click();
|
|
|
|
await page.getByTestId("notes_input").click();
|
|
await page.getByTestId("notes_input").fill("Changed notes");
|
|
|
|
await page.getByTestId(`team_username_select_${userToSecond.id}`).click();
|
|
|
|
// send request
|
|
await saveAndWaitForResponse(page);
|
|
|
|
// expect entry with new username exist.
|
|
await expect(page.locator(`data-testid=table-redirect-${userToSecond.username}`)).toBeVisible();
|
|
|
|
// expect new note to be present.
|
|
await expect(page.locator(`data-testid=ooo-entry-note-${userToSecond.username}`)).toBeVisible();
|
|
await expect(page.locator(`data-testid=ooo-entry-note-${userToSecond.username}`)).toContainText(
|
|
"Changed notes"
|
|
);
|
|
});
|
|
|
|
test("Profile redirection", async ({ page, users }) => {
|
|
const user = await users.create({ name: "userOne" });
|
|
const userTo = await users.create({ name: "userTwo" });
|
|
const uuid = uuidv4();
|
|
await prisma.outOfOfficeEntry.create({
|
|
data: {
|
|
start: dayjs().startOf("day").toDate(),
|
|
end: dayjs().startOf("day").add(1, "w").toDate(),
|
|
uuid,
|
|
user: { connect: { id: user.id } },
|
|
toUser: { connect: { id: userTo.id } },
|
|
createdAt: new Date(),
|
|
reason: {
|
|
connect: {
|
|
id: 1,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
await page.goto(`/${user.username}`);
|
|
|
|
const eventTypeLink = page.locator('[data-testid="event-type-link"]').first();
|
|
await eventTypeLink.click();
|
|
|
|
await expect(page.getByTestId("away-emoji")).toBeTruthy();
|
|
});
|
|
|
|
test("User can create Entry for past", async ({ page, users }) => {
|
|
const user = await users.create({ name: "userOne" });
|
|
|
|
await user.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
const reasonListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeReasonList?batch=1") && response.status() === 200
|
|
);
|
|
await page.getByTestId("add_entry_ooo").click();
|
|
await reasonListRespPromise;
|
|
|
|
await page.locator('[data-testid="date-range"]').click();
|
|
|
|
await selectToAndFromDates(page, "13", "22", true);
|
|
|
|
// send request
|
|
await saveAndWaitForResponse(page);
|
|
|
|
const ooo = await prisma.outOfOfficeEntry.findMany({
|
|
where: {
|
|
userId: user.id,
|
|
},
|
|
orderBy: {
|
|
createdAt: "desc",
|
|
},
|
|
select: {
|
|
start: true,
|
|
end: true,
|
|
},
|
|
take: 1,
|
|
});
|
|
|
|
const latestEntry = ooo[0];
|
|
|
|
const currentDate = dayjs();
|
|
const fromDate = dayjs(latestEntry.start);
|
|
const toDate = dayjs(latestEntry.end);
|
|
|
|
expect(toDate.isBefore(currentDate)).toBe(true);
|
|
expect(fromDate.isBefore(currentDate)).toBe(true);
|
|
});
|
|
|
|
test("User can create overriding entries", async ({ page, users }) => {
|
|
const user = await users.create({ name: "userOne" });
|
|
|
|
await user.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
const reasonListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeReasonList?batch=1") && response.status() === 200
|
|
);
|
|
await page.getByTestId("add_entry_ooo").click();
|
|
await reasonListRespPromise;
|
|
|
|
await page.locator('[data-testid="date-range"]').click();
|
|
|
|
await selectToAndFromDates(page, "13", "22");
|
|
|
|
// send request
|
|
await saveAndWaitForResponse(page);
|
|
await expect(page.locator(`data-testid=table-redirect-n-a`)).toBeVisible();
|
|
|
|
// add another entry
|
|
await entriesListRespPromise;
|
|
await page.getByTestId("add_entry_ooo").click();
|
|
await reasonListRespPromise;
|
|
|
|
await page.locator('[data-testid="date-range"]').click();
|
|
|
|
await selectToAndFromDates(page, "11", "24");
|
|
|
|
// send request
|
|
await saveAndWaitForResponse(page);
|
|
|
|
await expect(page.locator(`data-testid=table-redirect-n-a`)).toHaveCount(2);
|
|
});
|
|
|
|
test("User cannot create duplicate entries", async ({ page, users }) => {
|
|
const user = await users.create({ name: "userOne" });
|
|
|
|
await user.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
const reasonListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeReasonList?batch=1") && response.status() === 200
|
|
);
|
|
await page.getByTestId("add_entry_ooo").click();
|
|
await reasonListRespPromise;
|
|
|
|
await page.locator('[data-testid="date-range"]').click();
|
|
|
|
await selectToAndFromDates(page, "13", "22");
|
|
|
|
// send request
|
|
await saveAndWaitForResponse(page);
|
|
await expect(page.locator(`data-testid=table-redirect-n-a`)).toBeVisible();
|
|
|
|
// add another entry
|
|
await entriesListRespPromise;
|
|
await page.getByTestId("add_entry_ooo").click();
|
|
await reasonListRespPromise;
|
|
|
|
await page.locator('[data-testid="date-range"]').click();
|
|
|
|
await selectToAndFromDates(page, "13", "22");
|
|
|
|
// send request
|
|
await saveAndWaitForResponse(page, 409);
|
|
});
|
|
|
|
test("User can create separate out of office entries for consecutive dates", async ({ page, users }) => {
|
|
const user = await users.create({ name: "userOne" });
|
|
await user.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
const addOOOButton = page.getByTestId("add_entry_ooo");
|
|
const dateButton = page.locator('[data-testid="date-range"]');
|
|
const reasonListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeReasonList?batch=1") && response.status() === 200
|
|
);
|
|
await addOOOButton.click();
|
|
await reasonListRespPromise;
|
|
|
|
//Creates 2 OOO entries:
|
|
//First OOO is created on Next month 1st - 3rd
|
|
await dateButton.click();
|
|
await selectDateAndCreateOOO(page, "1", "3");
|
|
await expect(page.locator(`data-testid=table-redirect-n-a`).nth(0)).toBeVisible();
|
|
|
|
//Second OOO is created on Next month 4th - 6th
|
|
await entriesListRespPromise;
|
|
await addOOOButton.click();
|
|
await reasonListRespPromise;
|
|
await dateButton.click();
|
|
await selectDateAndCreateOOO(page, "4", "6");
|
|
await expect(page.locator(`data-testid=table-redirect-n-a`).nth(1)).toBeVisible();
|
|
});
|
|
|
|
test("User can create consecutive reverse redirect OOOs", async ({ page, users }) => {
|
|
const teamMatesObj = [{ name: "member-1" }, { name: "member-2" }];
|
|
const owner = await users.create(
|
|
{ name: "owner" },
|
|
{
|
|
hasTeam: true,
|
|
isOrg: true,
|
|
teammates: teamMatesObj,
|
|
}
|
|
);
|
|
const member1User = users.get().find((user) => user.name === "member-1");
|
|
|
|
await owner.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
const addOOOButton = page.getByTestId("add_entry_ooo");
|
|
const dateButton = page.locator('[data-testid="date-range"]');
|
|
const reasonListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeReasonList?batch=1") && response.status() === 200
|
|
);
|
|
await addOOOButton.click();
|
|
await reasonListRespPromise;
|
|
|
|
//As owner,OOO is created on Next month 1st - 3rd, forwarding to 'member-1'
|
|
await dateButton.click();
|
|
await selectDateAndCreateOOO(page, "1", "3", "member-1");
|
|
await expect(
|
|
page.locator(`data-testid=table-redirect-${member1User?.username ?? "n-a"}`).nth(0)
|
|
).toBeVisible();
|
|
|
|
//As member1, OOO is created on Next month 4th - 5th, forwarding to 'owner'
|
|
await member1User?.apiLogin();
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
await addOOOButton.click();
|
|
await reasonListRespPromise;
|
|
await dateButton.click();
|
|
await selectDateAndCreateOOO(page, "4", "5", "owner");
|
|
await expect(page.locator(`data-testid=table-redirect-${owner.username ?? "n-a"}`).nth(0)).toBeVisible();
|
|
});
|
|
|
|
test("User cannot create infinite or overlapping reverse redirect OOOs", async ({ page, users }) => {
|
|
const t = await localize("en");
|
|
const teamMatesObj = [{ name: "member-1" }, { name: "member-2" }];
|
|
const owner = await users.create(
|
|
{ name: "owner" },
|
|
{
|
|
hasTeam: true,
|
|
isOrg: true,
|
|
teammates: teamMatesObj,
|
|
}
|
|
);
|
|
const member1User = users.get().find((user) => user.name === "member-1");
|
|
|
|
await owner.apiLogin();
|
|
|
|
const entriesListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeEntriesList") && response.status() === 200
|
|
);
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
|
|
const addOOOButton = page.getByTestId("add_entry_ooo");
|
|
const dateButton = page.locator('[data-testid="date-range"]');
|
|
const reasonListRespPromise = page.waitForResponse(
|
|
(response) => response.url().includes("outOfOfficeReasonList?batch=1") && response.status() === 200
|
|
);
|
|
await addOOOButton.click();
|
|
await reasonListRespPromise;
|
|
|
|
//As owner,OOO is created on Next month 1st - 3rd, forwarding to 'member-1'
|
|
await dateButton.click();
|
|
await selectDateAndCreateOOO(page, "1", "3", "member-1");
|
|
await expect(
|
|
page.locator(`data-testid=table-redirect-${member1User?.username ?? "n-a"}`).nth(0)
|
|
).toBeVisible();
|
|
|
|
//As member1, expect error while OOO is created on Next month 2nd - 5th, forwarding to 'owner'
|
|
await member1User?.apiLogin();
|
|
await page.goto("/settings/my-account/out-of-office");
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await entriesListRespPromise;
|
|
await addOOOButton.click();
|
|
await reasonListRespPromise;
|
|
await dateButton.click();
|
|
await selectDateAndCreateOOO(page, "2", "5", "owner", 400);
|
|
await expect(page.locator(`text=${t("booking_redirect_infinite_not_allowed")}`)).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
async function saveAndWaitForResponse(page: Page, expectedStatusCode = 200) {
|
|
await submitAndWaitForResponse(page, "/api/trpc/viewer/outOfOfficeCreateOrUpdate?batch=1", {
|
|
action: () => page.getByTestId("create-or-edit-entry-ooo-redirect").click(),
|
|
expectedStatusCode,
|
|
});
|
|
}
|
|
|
|
async function selectToAndFromDates(page: Page, fromDate: string, toDate: string, isRangeInPast = false) {
|
|
const month = isRangeInPast ? "previous" : "next";
|
|
|
|
await page.locator(`button[name="${month}-month"]`).click();
|
|
|
|
await page.locator(`button[name="day"]:text-is("${fromDate}")`).nth(0).click();
|
|
await page.locator(`button[name="day"]:text-is("${toDate}")`).nth(0).click();
|
|
}
|
|
|
|
async function selectDateAndCreateOOO(
|
|
page: Page,
|
|
fromDate: string,
|
|
toDate: string,
|
|
redirectToUser?: string,
|
|
expectedStatusCode = 200
|
|
) {
|
|
const t = await localize("en");
|
|
await page.locator(`button[name="next-month"]`).click();
|
|
await page.locator(`button[name="day"]:text-is("${fromDate}")`).nth(0).click();
|
|
await page.locator(`button[name="day"]:text-is("${toDate}")`).nth(0).click();
|
|
await page.locator(`text=${t("create_an_out_of_office")}`).click();
|
|
await page.getByTestId("reason_select").click();
|
|
await page.getByTestId("select-option-4").click();
|
|
await page.getByTestId("notes_input").click();
|
|
await page.getByTestId("notes_input").fill("Demo notes");
|
|
if (redirectToUser) {
|
|
await page.getByTestId("profile-redirect-switch").click();
|
|
await page.locator(`text=${redirectToUser}`).click();
|
|
}
|
|
await saveAndWaitForResponse(page, expectedStatusCode);
|
|
}
|