* ui playground * add types to ui * push avatar ignore ts and eslint errors * feat: V3 UI avatar (#18997) * avatar playground * add overflow-hidden back for loading indicator * update lock * feat: v3/badge (#18999) * badge intro * fix text on small badge * add dark mode warning * feat: v3/button (#19014) * TOC * wip buttons * shadows * secondary * semantic colours * minimal buttons * update disabled states * checkbox * dropdown v3 * load icons * fix design sections * fix icon gap * fix * remove client component from rsc * use peer as avatar * gif * fix button gap - dropdown iocn * support start icon in dropdowns * add alert demo + v3 component + darken semantic text * base inputs and text field * wip input * input field addon/suffix * toast page * wip select * match designs for input and select rounded * fix * fix select isloading transform * fix multi icons transition rotation * fix group heading * range slider demo + range slider * range slider popover design * v3 dialog * fix slider thumb size * wrap in suspense for useSearchParam difference in next14+ * base of tabs + horizontal tabs * fix select sizing to match input 36px * fix dark mode hover on button * target domState class for light/dark so we work on portal elements * fix default bg on popover * lightmode toasts + eleveation token * empty screen * intro to split button concept * mass colour updates * intro to themes * remove i18n from label with name * bouncy buttons * feat: small select * nav item dumb component * full demo * settings demo * fix tabs * fix mt * fix dropdown feedback from loom * alert fixes mentioned in loom * push font-cal * move to fumadocs * fix avatar * colors table * add typeography * icon + spacing guide * add border radius and shadow tables * fix colours and border radius * remove prose undline via fumas fork * fix icon buttons sizes * button group component * fix horizontal tabs * tab fixes * switches * pagination component * show toasts components raw * sync with airtable * add use client * 10px on input again * 10px in select also * fix input height * fix files merge * fix loading state for icon buttons * fix button fab * Enable addon to now overflow to next line in DS * use cal font for dialog text * add avatar square variant * fix select padding and menu size * fix type errors * fix type errors * add switch small size and fix type errors * fix ref types on input * update web styles * remove addonFilled to fix type errors * push dialog fixes * fix checkbox display * remove prose from alert examples * remove prose from all our component examples * badge rounded-lg on lg size * update colors * force w/h on icon buttons instead of inheriting * remove border-focus * remove border-focus from tailwind config * update tokens to match new colours * add new border subtle tokens and add to toasts * fix lg button padding * fix and drastically improve rtl rests * multi select use badges * use badge design for select pills * fix avatar alignment * fix tests and remove redudant ones * fix tests and remove redudant ones * update atoms globals css * extract tokens.css * extract app.css * force flex for atoms * add @calcom/ui/css * revert ui playground styles abstraction * fix input focus states * fix focus states for buttons * feat: v3/app work (#19233) * globals.css for app + event types listing search * few app wide NITS * fix select foucs within styling * fix formbuilder switch styles * fix disabled state of nested inputs * workflow fixes * webhook form fixes * i18n on tabs info * booking filters + tab * fix filters spacing * make first item so we get correct border radius if not default * round radio group in create event types + make button small * profile page * update editor br * button nits in existing places in the app * fix timezone select and multilist in guest form * fix text area on public booking page * size small event types create * margin on routing filter * filters improvments * routing insights fixes * fix date filter range * ooo fixes * fix teams/members bulk actions * create team UI * i18n in useTabs instead of in tab component now * Add event type setup border to availability * fix bulk actions on org members table * onboarding fixes * fix input types with ref stripe comparision * i18n fixes for platform * update globals of atoms * fix form builder firstname/lastname switch + correct footer useage * fix select * Revert "add @calcom/ui/css" This reverts commit 0dd8f9012907a278cfede4a98f289cb0744c9244. * revert tokens css * Revert "extract app.css" This reverts commit 10b998604816ad3310c24b36a02a638dabc5be24. * linear nits * workflow dropdown * fix page theme heading sizes to match cards * fix stepper for team creation * adjust margin to FilterSearchField * fix ooo icon * fix booker and dark mode due to darkgray 50 * more dark mode fixes * add size prop --------- Co-authored-by: Eunjae Lee <hey@eunjae.dev> Co-authored-by: Morgan Vernay <morgan@cal.com> * remove ui-playground * fix grow missing from merge * add search icon data-test-id back to search * fix FormAction style * fix datatestid for apps and tabs * change selector for team invitation e2e * fix use data-testid from item instaed of using names * rename upper case O * fix unit tests data-testid * fix unit tests data-testid * more data-testid changes * add data-testid to apps tab * data-testid to installed appPage * fix embed tests * fix filter-popover-trigger * use text-error and use dataId for locator * more e2e improvment * pass in single testId as button is now duplicate * show field-error instead * add test to eventtype tabs * Add css vars to embed too\ * add scrollbar and change dialog overlay colour * fix type * use attribute instead of class for tab detection * await promise * use new tokens for sprite generation * add correct testing Ids to e2e instead of button:text * use testIds for creation of orgs * Webhook receiver wait for 10 seconds --------- Co-authored-by: Eunjae Lee <hey@eunjae.dev> Co-authored-by: Morgan Vernay <morgan@cal.com> Co-authored-by: Hariom <hariombalhara@gmail.com> Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
633 lines
22 KiB
TypeScript
633 lines
22 KiB
TypeScript
import type { Browser, Page } from "@playwright/test";
|
|
import { expect } from "@playwright/test";
|
|
|
|
import prisma from "@calcom/prisma";
|
|
import { MembershipRole } from "@calcom/prisma/client";
|
|
import { SchedulingType } from "@calcom/prisma/enums";
|
|
|
|
import { test } from "../lib/fixtures";
|
|
import { moveUserToOrg } from "../lib/orgMigration";
|
|
import { bookTeamEvent, doOnOrgDomain, expectPageToBeNotFound, getInviteLink } from "../lib/testUtils";
|
|
import { expectInvitationEmailToBeReceived } from "./expects";
|
|
|
|
test.describe.configure({ mode: "parallel" });
|
|
|
|
test.afterEach(async ({ users, orgs }) => {
|
|
await users.deleteAll();
|
|
await orgs.deleteAll();
|
|
});
|
|
|
|
test.describe("Organization", () => {
|
|
test.describe("Email not matching orgAutoAcceptEmail", () => {
|
|
test("nonexisting user invited to an organization", async ({ browser, page, users, emails }) => {
|
|
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true });
|
|
const { team: org } = await orgOwner.getOrgMembership();
|
|
await orgOwner.apiLogin();
|
|
await page.goto(`/settings/organizations/${org.slug}/members`);
|
|
|
|
await test.step("By email", async () => {
|
|
const invitedUserEmail = users.trackEmail({ username: "rick", domain: "domain.com" });
|
|
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
|
const usernameDerivedFromEmail = `${invitedUserEmail.split("@")[0]}-domain`;
|
|
|
|
await inviteAnEmail(page, invitedUserEmail);
|
|
const inviteLink = await expectInvitationEmailToBeReceived(
|
|
page,
|
|
emails,
|
|
invitedUserEmail,
|
|
`${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`,
|
|
"signup?token"
|
|
);
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: false,
|
|
email: invitedUserEmail,
|
|
});
|
|
|
|
assertInviteLink(inviteLink);
|
|
await signupFromEmailInviteLink({
|
|
browser,
|
|
inviteLink,
|
|
expectedEmail: invitedUserEmail,
|
|
expectedUsername: usernameDerivedFromEmail,
|
|
});
|
|
|
|
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
|
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
});
|
|
|
|
await test.step("By invite link", async () => {
|
|
const inviteLink = await copyInviteLink(page);
|
|
const email = users.trackEmail({ username: "rick", domain: "domain.com" });
|
|
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
|
const usernameDerivedFromEmail = `${email.split("@")[0]}-domain`;
|
|
await signupFromInviteLink({ browser, inviteLink, email });
|
|
const dbUser = await prisma.user.findUnique({ where: { email } });
|
|
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email,
|
|
});
|
|
});
|
|
});
|
|
|
|
// This test is already covered by booking.e2e.ts where existing user is invited and his booking links are tested.
|
|
// We can re-test here when we want to test some more scenarios.
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
test("existing user invited to an organization", () => {});
|
|
|
|
test("nonexisting user invited to a Team inside organization", async ({
|
|
browser,
|
|
page,
|
|
users,
|
|
emails,
|
|
}) => {
|
|
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, hasSubteam: true });
|
|
await orgOwner.apiLogin();
|
|
const { team } = await orgOwner.getFirstTeamMembership();
|
|
const { team: org } = await orgOwner.getOrgMembership();
|
|
|
|
await test.step("By email", async () => {
|
|
await page.goto(`/settings/teams/${team.id}/members`);
|
|
const invitedUserEmail = users.trackEmail({ username: "rick", domain: "domain.com" });
|
|
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
|
const usernameDerivedFromEmail = `${invitedUserEmail.split("@")[0]}-domain`;
|
|
await inviteAnEmail(page, invitedUserEmail, true);
|
|
await expectUserToBeAMemberOfTeam({
|
|
page,
|
|
teamId: team.id,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: false,
|
|
email: invitedUserEmail,
|
|
});
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: false,
|
|
email: invitedUserEmail,
|
|
});
|
|
|
|
const inviteLink = await expectInvitationEmailToBeReceived(
|
|
page,
|
|
emails,
|
|
invitedUserEmail,
|
|
`${team.name}'s admin invited you to join the team ${team.name} of organization ${org.name} on Cal.com`,
|
|
"signup?token"
|
|
);
|
|
|
|
assertInviteLink(inviteLink);
|
|
|
|
await signupFromEmailInviteLink({
|
|
browser,
|
|
inviteLink,
|
|
expectedEmail: invitedUserEmail,
|
|
expectedUsername: usernameDerivedFromEmail,
|
|
});
|
|
|
|
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
|
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
|
|
|
await expectUserToBeAMemberOfTeam({
|
|
page,
|
|
teamId: team.id,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
});
|
|
|
|
await test.step("By invite link", async () => {
|
|
await page.goto(`/settings/teams/${team.id}/members`);
|
|
const inviteLink = await copyInviteLink(page, true);
|
|
const email = users.trackEmail({ username: "rick", domain: "domain.com" });
|
|
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
|
const usernameDerivedFromEmail = `${email.split("@")[0]}-domain`;
|
|
await signupFromInviteLink({ browser, inviteLink, email });
|
|
|
|
const dbUser = await prisma.user.findUnique({ where: { email } });
|
|
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
|
await expectUserToBeAMemberOfTeam({
|
|
teamId: team.id,
|
|
page,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: email,
|
|
});
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: email,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Email matching orgAutoAcceptEmail and a Verified Organization with DNS Setup Done", () => {
|
|
test("nonexisting user is invited to Org", async ({ browser, page, users, emails }) => {
|
|
const orgOwner = await users.create(undefined, {
|
|
hasTeam: true,
|
|
isOrg: true,
|
|
isOrgVerified: true,
|
|
isDnsSetup: true,
|
|
});
|
|
const { team: org } = await orgOwner.getOrgMembership();
|
|
await orgOwner.apiLogin();
|
|
await page.goto(`/settings/organizations/${org.slug}/members`);
|
|
|
|
await test.step("By email", async () => {
|
|
const invitedUserEmail = users.trackEmail({ username: "rick", domain: "example.com" });
|
|
const usernameDerivedFromEmail = invitedUserEmail.split("@")[0];
|
|
await inviteAnEmail(page, invitedUserEmail);
|
|
const inviteLink = await expectInvitationEmailToBeReceived(
|
|
page,
|
|
emails,
|
|
invitedUserEmail,
|
|
`${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`,
|
|
"signup?token"
|
|
);
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
|
|
assertInviteLink(inviteLink);
|
|
await signupFromEmailInviteLink({
|
|
browser,
|
|
inviteLink,
|
|
expectedEmail: invitedUserEmail,
|
|
expectedUsername: usernameDerivedFromEmail,
|
|
});
|
|
|
|
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
|
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
});
|
|
|
|
await test.step("By invite link", async () => {
|
|
const inviteLink = await copyInviteLink(page);
|
|
const email = users.trackEmail({ username: "rick", domain: "example.com" });
|
|
const usernameDerivedFromEmail = email.split("@")[0];
|
|
await signupFromInviteLink({ browser, inviteLink, email });
|
|
|
|
const dbUser = await prisma.user.findUnique({ where: { email } });
|
|
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email,
|
|
});
|
|
});
|
|
});
|
|
|
|
// Such a user has user.username changed directly in addition to having the new username in the profile.username
|
|
test("existing user migrated to an organization", async ({ users, page, emails }) => {
|
|
const orgOwner = await users.create(undefined, {
|
|
hasTeam: true,
|
|
isOrg: true,
|
|
isOrgVerified: true,
|
|
isDnsSetup: true,
|
|
});
|
|
const { team: org } = await orgOwner.getOrgMembership();
|
|
await orgOwner.apiLogin();
|
|
const { existingUser } = await test.step("Invite an existing user to an organization", async () => {
|
|
const existingUser = await users.create({
|
|
username: "john",
|
|
emailDomain: org.organizationSettings?.orgAutoAcceptEmail ?? "",
|
|
name: "John Outside Organization",
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: existingUser,
|
|
targetOrg: {
|
|
username: `${existingUser.username}-org`,
|
|
id: org.id,
|
|
membership: {
|
|
role: MembershipRole.MEMBER,
|
|
accepted: true,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
return { existingUser };
|
|
});
|
|
|
|
await test.step("Signing up with the previous username of the migrated user - shouldn't be allowed", async () => {
|
|
await page.goto("/signup");
|
|
await expect(page.locator("text=Create your account")).toBeVisible();
|
|
await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible();
|
|
await page.locator('[data-testid="continue-with-email-button"]').click();
|
|
await expect(page.locator('[data-testid="signup-submit-button"]')).toBeVisible();
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
await page.locator('input[name="username"]').fill(existingUser.username!);
|
|
await page
|
|
.locator('input[name="email"]')
|
|
.fill(`${existingUser.username}-differnet-email@example.com`);
|
|
await page.locator('input[name="password"]').fill("Password99!");
|
|
await expect(page.locator('button[type="submit"]')).toBeDisabled();
|
|
});
|
|
});
|
|
|
|
test("nonexisting user is invited to a team inside organization", async ({
|
|
browser,
|
|
page,
|
|
users,
|
|
emails,
|
|
}) => {
|
|
const orgOwner = await users.create(undefined, {
|
|
hasTeam: true,
|
|
isOrg: true,
|
|
hasSubteam: true,
|
|
isOrgVerified: true,
|
|
isDnsSetup: true,
|
|
});
|
|
const { team: org } = await orgOwner.getOrgMembership();
|
|
const { team } = await orgOwner.getFirstTeamMembership();
|
|
|
|
await orgOwner.apiLogin();
|
|
|
|
await test.step("By email", async () => {
|
|
await page.goto(`/settings/teams/${team.id}/members`);
|
|
const invitedUserEmail = users.trackEmail({ username: "rick", domain: "example.com" });
|
|
const usernameDerivedFromEmail = invitedUserEmail.split("@")[0];
|
|
await inviteAnEmail(page, invitedUserEmail, true);
|
|
await expectUserToBeAMemberOfTeam({
|
|
page,
|
|
teamId: team.id,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
const inviteLink = await expectInvitationEmailToBeReceived(
|
|
page,
|
|
emails,
|
|
invitedUserEmail,
|
|
`${team.name}'s admin invited you to join the team ${team.name} of organization ${org.name} on Cal.com`,
|
|
"signup?token"
|
|
);
|
|
|
|
assertInviteLink(inviteLink);
|
|
|
|
await signupFromEmailInviteLink({
|
|
browser,
|
|
inviteLink,
|
|
expectedEmail: invitedUserEmail,
|
|
expectedUsername: usernameDerivedFromEmail,
|
|
});
|
|
|
|
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
|
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
|
|
|
await expectUserToBeAMemberOfTeam({
|
|
page,
|
|
teamId: team.id,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: invitedUserEmail,
|
|
});
|
|
});
|
|
|
|
await test.step("By invite link", async () => {
|
|
await page.goto(`/settings/teams/${team.id}/members`);
|
|
|
|
const inviteLink = await copyInviteLink(page, true);
|
|
const email = users.trackEmail({ username: "rick", domain: "example.com" });
|
|
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
|
const usernameDerivedFromEmail = `${email.split("@")[0]}`;
|
|
|
|
await signupFromInviteLink({ browser, inviteLink, email });
|
|
|
|
const dbUser = await prisma.user.findUnique({ where: { email } });
|
|
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
|
await expectUserToBeAMemberOfTeam({
|
|
teamId: team.id,
|
|
page,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: email,
|
|
});
|
|
await expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug: org.slug,
|
|
username: usernameDerivedFromEmail,
|
|
role: "member",
|
|
isMemberShipAccepted: true,
|
|
email: email,
|
|
});
|
|
});
|
|
});
|
|
|
|
test("can book an event with auto accepted invitee (not completed on-boarding) added as fixed host.", async ({
|
|
page,
|
|
users,
|
|
}) => {
|
|
const orgOwner = await users.create(undefined, {
|
|
hasTeam: true,
|
|
isOrg: true,
|
|
hasSubteam: true,
|
|
isOrgVerified: true,
|
|
isDnsSetup: true,
|
|
orgRequestedSlug: "example",
|
|
schedulingType: SchedulingType.ROUND_ROBIN,
|
|
});
|
|
const { team: org } = await orgOwner.getOrgMembership();
|
|
const { team } = await orgOwner.getFirstTeamMembership();
|
|
|
|
await orgOwner.apiLogin();
|
|
await page.goto(`/settings/teams/${team.id}/members`);
|
|
const invitedUserEmail = users.trackEmail({ username: "rick", domain: "example.com" });
|
|
await inviteAnEmail(page, invitedUserEmail, true);
|
|
|
|
//add invitee as fixed host to team event
|
|
const teamEvent = await orgOwner.getFirstTeamEvent(team.id);
|
|
await page.goto(`/event-types/${teamEvent.id}?tabName=team`);
|
|
await page.locator('[data-testid="fixed-hosts-switch"]').click();
|
|
await page.locator('[data-testid="fixed-hosts-select"]').click();
|
|
await page.locator(`text="${invitedUserEmail}"`).click();
|
|
await page.locator('[data-testid="update-eventtype"]').click();
|
|
await page.waitForResponse("/api/trpc/eventTypes/update?batch=1");
|
|
|
|
await expectPageToBeNotFound({ page, url: `/team/${team.slug}/${teamEvent.slug}` });
|
|
await doOnOrgDomain(
|
|
{
|
|
orgSlug: org.slug,
|
|
page,
|
|
},
|
|
async ({ page, goToUrlWithErrorHandling }) => {
|
|
const result = await goToUrlWithErrorHandling(`/team/${team.slug}/${teamEvent.slug}`);
|
|
await bookTeamEvent({ page, team, event: teamEvent });
|
|
await expect(page.getByText(invitedUserEmail, { exact: true })).toBeVisible();
|
|
return { url: result.url };
|
|
}
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
async function signupFromInviteLink({
|
|
browser,
|
|
inviteLink,
|
|
email,
|
|
}: {
|
|
browser: Browser;
|
|
inviteLink: string;
|
|
email: string;
|
|
}) {
|
|
const context = await browser.newContext();
|
|
const inviteLinkPage = await context.newPage();
|
|
await inviteLinkPage.goto(inviteLink);
|
|
await expect(inviteLinkPage.locator("text=Create your account")).toBeVisible();
|
|
|
|
// Check required fields
|
|
const button = inviteLinkPage.locator("button[type=submit][disabled]");
|
|
await expect(button).toBeVisible(); // email + 3 password hints
|
|
|
|
await inviteLinkPage.locator("input[name=email]").fill(email);
|
|
await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
|
await inviteLinkPage.locator("button[type=submit]").click();
|
|
await inviteLinkPage.waitForURL("/getting-started");
|
|
return { email };
|
|
}
|
|
|
|
export async function signupFromEmailInviteLink({
|
|
browser,
|
|
inviteLink,
|
|
expectedUsername,
|
|
expectedEmail,
|
|
}: {
|
|
browser: Browser;
|
|
inviteLink: string;
|
|
expectedUsername?: string;
|
|
expectedEmail?: string;
|
|
}) {
|
|
// Follow invite link in new window
|
|
const context = await browser.newContext();
|
|
const signupPage = await context.newPage();
|
|
|
|
signupPage.goto(inviteLink);
|
|
await expect(signupPage.locator("text=Create your account")).toBeVisible();
|
|
await expect(signupPage.locator(`[data-testid="signup-usernamefield"]`)).toBeDisabled();
|
|
// await for value. initial value is ""
|
|
if (expectedUsername) {
|
|
await expect(signupPage.locator(`[data-testid="signup-usernamefield"]`)).toHaveValue(expectedUsername);
|
|
}
|
|
|
|
await expect(signupPage.locator(`[data-testid="signup-emailfield"]`)).toBeDisabled();
|
|
if (expectedEmail) {
|
|
await expect(signupPage.locator(`[data-testid="signup-emailfield"]`)).toHaveValue(expectedEmail);
|
|
}
|
|
|
|
// Check required fields
|
|
await signupPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
|
await signupPage.locator("button[type=submit]").click();
|
|
await signupPage.waitForURL("/getting-started?from=signup");
|
|
await context.close();
|
|
await signupPage.close();
|
|
}
|
|
|
|
async function inviteAnEmail(page: Page, invitedUserEmail: string, teamPage?: boolean) {
|
|
if (teamPage) {
|
|
await page.getByTestId("new-member-button").click();
|
|
} else {
|
|
await page.getByTestId("new-organization-member-button").click();
|
|
}
|
|
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
|
|
const submitPromise = page.waitForResponse("/api/trpc/teams/inviteMember?batch=1");
|
|
await page.getByTestId("invite-new-member-button").click();
|
|
const response = await submitPromise;
|
|
expect(response.status()).toBe(200);
|
|
}
|
|
|
|
async function expectUserToBeAMemberOfOrganization({
|
|
page,
|
|
orgSlug,
|
|
username,
|
|
email,
|
|
role,
|
|
isMemberShipAccepted,
|
|
}: {
|
|
page: Page;
|
|
orgSlug: string | null;
|
|
username: string;
|
|
role: string;
|
|
isMemberShipAccepted: boolean;
|
|
email: string;
|
|
}) {
|
|
// Check newly invited member is not pending anymore
|
|
await page.goto(`/settings/organizations/${orgSlug}/members`);
|
|
await expect(page.locator(`[data-testid="member-${username}-username"]`)).toHaveText(username);
|
|
await expect(page.locator(`[data-testid="member-${username}-email"]`)).toHaveText(email);
|
|
expect((await page.locator(`[data-testid="member-${username}-role"]`).textContent())?.toLowerCase()).toBe(
|
|
role.toLowerCase()
|
|
);
|
|
if (isMemberShipAccepted) {
|
|
await expect(page.locator(`[data-testid2="member-${username}-pending"]`)).toBeHidden();
|
|
} else {
|
|
await expect(page.locator(`[data-testid2="member-${username}-pending"]`)).toBeVisible();
|
|
}
|
|
}
|
|
|
|
async function expectUserToBeAMemberOfTeam({
|
|
page,
|
|
teamId,
|
|
email,
|
|
role,
|
|
username,
|
|
isMemberShipAccepted,
|
|
}: {
|
|
page: Page;
|
|
username: string;
|
|
role: string;
|
|
teamId: number;
|
|
isMemberShipAccepted: boolean;
|
|
email: string;
|
|
}) {
|
|
// Check newly invited member is not pending anymore
|
|
await page.goto(`/settings/teams/${teamId}/members`);
|
|
await page.reload();
|
|
await page.waitForLoadState("domcontentloaded");
|
|
expect(
|
|
(
|
|
await page
|
|
.locator(
|
|
`[data-testid="member-${username}"] [data-testid="${
|
|
isMemberShipAccepted ? "member-email" : `email-${email.replace("@", "")}-pending`
|
|
}"]`
|
|
)
|
|
.textContent()
|
|
)?.toLowerCase()
|
|
).toBe(email.toLowerCase());
|
|
if (isMemberShipAccepted) {
|
|
await expect(page.locator(`[data-testid="email-${email.replace("@", "")}-pending"]`)).toBeHidden();
|
|
} else {
|
|
await expect(page.locator(`[data-testid="email-${email.replace("@", "")}-pending"]`)).toBeVisible();
|
|
}
|
|
}
|
|
|
|
function assertInviteLink(inviteLink: string | null | undefined): asserts inviteLink is string {
|
|
if (!inviteLink) throw new Error("Invite link not found");
|
|
}
|
|
|
|
async function copyInviteLink(page: Page, teamPage?: boolean) {
|
|
if (teamPage) {
|
|
await page.getByTestId("new-member-button").click();
|
|
} else {
|
|
await page.getByTestId("new-organization-member-button").click();
|
|
}
|
|
const inviteLink = await getInviteLink(page);
|
|
return inviteLink;
|
|
}
|