Files
cal.diy/apps/web/playwright/fixtures/users.ts
T
+4 d55ffe8d9d Feat: v2 App Store / Installed Apps (#4220)
* First commit

* Additional changes to meet designs

* Fixing app details disconnect button
App Store category filtering fixed

* Further changes for the fix

* Progress on various fronts

* Fixing e2e tests

* second try fixing e2e tests

* Yet again trying to fix e2e tests

* Missing action for non-credential flow

* Adding missing Add button

* Final tweaks and fixes

* Missing button in empty screen

* fix border radius

* EmptyScreen for calendar

* Dynamic installed app return page with highlights + other fixes

* Removing unneeded code

* App Store V2 Improvements over feat/v2-installed-apps (#4379)

* Removing unneeded text

* Fixing app details disconnect button and number of apps (#4305)

* V2 Settings Shell - Add Teams Section & UI fixes (#4347)

* Add teams to sidebar and fix UI

* Clean up

* V2 Multi-select (Team Select) (#4324)

* --init

* design improved

* further fine tuning

* more fixes

* removed extra JSX tag

* added story

* NIT

* revert to use of CheckedTeamSelect

* Removes comments

Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* fix: toggle alligment (#4361)

* fix: add checked tranform for switch (#4357)

* fixed input size on mobile, fixed settings (#4360)

* fixing type errors

* Mobile fixes

* Tests fixes

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>

* Feat/team owner booking (#3999)

* WIP: testing queries

* feat: add badge

* fix: get only id

* refactor: get bookings query

* WIP: display attendees added

* fix: add type

* Adds skeleton loader to workflows (#4402)

* fix event types skeleton loader

* fix margin in event types

* fix skeleton loader in bookings

* add skeleton loader for workflow list

* add skeleton loader for editing page

* fix border radius of skeleton loader

* fix mobile view workflow list

* add mobile view for skeleton list

* make multi select full with in mobile view

* mobile view for edit skeleton loader

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* V2 settings teams (Profil, Members, Appearance View) (#4350)

* add team profile

* first version for team members page

* finish up design of member list item

* fix dialog buttons

* add missing seats and upgrading information

* add v2 dialog for changing role

* finish basic version of member's schedule

* remove modalContainer

* design fixes team profile page

* show only team info to non admins

* allow all member to check availabilities

* make time available heading sticky

* add dropdown for mobile view

* create team appearance view

* finish appearance page

* use settings layout and add danger zone for member

* add fallback logo

* Add teams to sidebar and fix UI

* add team invitations

* Clean up

* code clean up

* add impersontation and disable autofocus on calendar

* improve team info

* refactor teaminvitelist code and fix leaving a team

* add team pages to settings shell

* add link to create new team

* small fixes

* clean up comments

* V2 Multi-select (Team Select) (#4324)

* --init

* design improved

* further fine tuning

* more fixes

* removed extra JSX tag

* added story

* NIT

* revert to use of CheckedTeamSelect

* Removes comments

Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* fix: toggle alligment (#4361)

* fix: add checked tranform for switch (#4357)

* fixed input size on mobile, fixed settings (#4360)

* fix image uploader button in safari

* code clean up

* fixing type errors

* Moved v2 team components to features

Adds deprecation notices

* Update SettingsLayout.tsx

* Migrated to features and build fixes

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>

* add rainbowkit form to v2 event-types (#4349)

Co-authored-by: Omar López <zomars@me.com>

* v1.9.5

* Fix/daterangepicker css (#4418)

* added styles from external stylesheet

* fixed a border color

* codacy improvements

* more codacy fix

* codacy why u be so dumb

* dynamic import rainbow (only if installed) (#4409)

* Tidyup app cards (#4428)

* Moving API to trpc query

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: hexcowboy <accounts@cowboy.dev>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* Abstracting one level the redirectUrl for apps

* Moving enum to a better/working place

* Another try

* Missing code

* Correcting categories look in apps page

* ui tweaks

* Breadcrumb wrong font-weight

* Toast is v1 component and copy updated

* Fix TS error

* Fix TS error

* Update AppsLayout.tsx

* Update InstalledAppsLayout.tsx

* translations and disconnect button with icon

* Reverting file

* Test e2e fix on app-store

* Missing testid in button

* Fix to click categories that are visible

* Going directly to category page as a patch

* Fixing stripe e2e test

* Disconnect is now empty with icon

* Disabled stripe test for now as the navigation is completely changed

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: hexcowboy <accounts@cowboy.dev>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-09-15 19:53:09 +00:00

323 lines
11 KiB
TypeScript

import type { Page, WorkerInfo } from "@playwright/test";
import type Prisma from "@prisma/client";
import { Prisma as PrismaType, UserPlan } from "@prisma/client";
import { hash } from "bcryptjs";
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { prisma } from "@calcom/prisma";
import { TimeZoneEnum } from "./types";
// Don't import hashPassword from app as that ends up importing next-auth and initializing it before NEXTAUTH_URL can be updated during tests.
export async function hashPassword(password: string) {
const hashedPassword = await hash(password, 12);
return hashedPassword;
}
type UserFixture = ReturnType<typeof createUserFixture>;
const userIncludes = PrismaType.validator<PrismaType.UserInclude>()({
eventTypes: true,
credentials: true,
routingForms: true,
});
const userWithEventTypes = PrismaType.validator<PrismaType.UserArgs>()({
include: userIncludes,
});
const seededForm = {
id: "948ae412-d995-4865-875a-48302588de03",
name: "Seeded Form - Pro",
};
type UserWithIncludes = PrismaType.UserGetPayload<typeof userWithEventTypes>;
// creates a user fixture instance and stores the collection
export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
const store = { users: [], page } as { users: UserFixture[]; page: typeof page };
return {
create: async (
opts?: CustomUserOpts | null,
scenario: {
seedRoutingForms?: boolean;
} = {}
) => {
const _user = await prisma.user.create({
data: await createUser(workerInfo, opts),
});
await prisma.eventType.create({
data: {
users: {
connect: {
id: _user.id,
},
},
title: "Paid",
slug: "paid",
length: 30,
price: 1000,
},
});
await prisma.eventType.create({
data: {
users: {
connect: {
id: _user.id,
},
},
title: "Opt in",
slug: "opt-in",
requiresConfirmation: true,
length: 30,
},
});
if (scenario.seedRoutingForms) {
await prisma.app_RoutingForms_Form.create({
data: {
routes: [
{
id: "8a898988-89ab-4cde-b012-31823f708642",
action: { type: "eventTypeRedirectUrl", value: "pro/30min" },
queryValue: {
id: "8a898988-89ab-4cde-b012-31823f708642",
type: "group",
children1: {
"8988bbb8-0123-4456-b89a-b1823f70c5ff": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["event-routing"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
},
},
},
},
},
{
id: "aa8aaba9-cdef-4012-b456-71823f70f7ef",
action: { type: "customPageMessage", value: "Custom Page Result" },
queryValue: {
id: "aa8aaba9-cdef-4012-b456-71823f70f7ef",
type: "group",
children1: {
"b99b8a89-89ab-4cde-b012-31823f718ff5": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["custom-page"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
},
},
},
},
},
{
id: "a8ba9aab-4567-489a-bcde-f1823f71b4ad",
action: { type: "externalRedirectUrl", value: "https://google.com" },
queryValue: {
id: "a8ba9aab-4567-489a-bcde-f1823f71b4ad",
type: "group",
children1: {
"998b9b9a-0123-4456-b89a-b1823f7232b9": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["external-redirect"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
},
},
},
},
},
{
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
action: { type: "customPageMessage", value: "Multiselect chosen" },
queryValue: {
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
type: "group",
children1: {
"b98a8abb-cdef-4012-b456-718262343d27": {
type: "rule",
properties: {
field: "d4292635-9f12-17b1-9153-c3a854649182",
value: [["Option-2"]],
operator: "multiselect_equals",
valueSrc: ["value"],
valueType: ["multiselect"],
},
},
},
},
},
{
id: "898899aa-4567-489a-bcde-f1823f708646",
action: { type: "customPageMessage", value: "Fallback Message" },
isFallback: true,
queryValue: { id: "898899aa-4567-489a-bcde-f1823f708646", type: "group" },
},
],
fields: [
{
id: "c4296635-9f12-47b1-8153-c3a854649182",
type: "text",
label: "Test field",
required: true,
},
{
id: "d4292635-9f12-17b1-9153-c3a854649182",
type: "multiselect",
label: "Multi Select",
identifier: "multi",
selectText: "Option-1\nOption-2",
required: false,
},
],
user: {
connect: {
id: _user.id,
},
},
name: seededForm.name,
},
});
}
const user = await prisma.user.findUniqueOrThrow({
where: { id: _user.id },
include: userIncludes,
});
const userFixture = createUserFixture(user, store.page!);
store.users.push(userFixture);
return userFixture;
},
get: () => store.users,
logout: async () => {
await page.goto("/auth/logout");
},
deleteAll: async () => {
const ids = store.users.map((u) => u.id);
await prisma.user.deleteMany({ where: { id: { in: ids } } });
store.users = [];
},
delete: async (id: number) => {
await prisma.user.delete({ where: { id } });
store.users = store.users.filter((b) => b.id !== id);
},
};
};
type JSONValue = string | number | boolean | { [x: string]: JSONValue } | Array<JSONValue>;
// creates the single user fixture
const createUserFixture = (user: UserWithIncludes, page: Page) => {
const store = { user, page };
// self is a reflective method that return the Prisma object that references this fixture.
const self = async () =>
(await prisma.user.findUnique({ where: { id: store.user.id }, include: { eventTypes: true } }))!;
return {
id: user.id,
username: user.username,
eventTypes: user.eventTypes,
routingForms: user.routingForms,
self,
login: async () => login({ ...(await self()), password: user.username }, store.page),
getPaymentCredential: async () => getPaymentCredential(store.page),
// ths is for developemnt only aimed to inject debugging messages in the metadata field of the user
debug: async (message: string | Record<string, JSONValue>) => {
await prisma.user.update({ where: { id: store.user.id }, data: { metadata: { debug: message } } });
},
delete: async () => (await prisma.user.delete({ where: { id: store.user.id } }))!,
};
};
type CustomUserOptsKeys = "username" | "password" | "plan" | "completedOnboarding" | "locale" | "name";
type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & { timeZone?: TimeZoneEnum };
// creates the actual user in the db.
const createUser = async (
workerInfo: WorkerInfo,
opts?: CustomUserOpts | null
): Promise<PrismaType.UserCreateInput> => {
// build a unique name for our user
const uname = `${opts?.username ?? opts?.plan?.toLocaleLowerCase() ?? UserPlan.PRO.toLowerCase()}-${
workerInfo.workerIndex
}-${Date.now()}`;
return {
username: uname,
name: opts?.name === undefined ? (opts?.plan ?? UserPlan.PRO).toUpperCase() : opts?.name,
plan: opts?.plan ?? UserPlan.PRO,
email: `${uname}@example.com`,
password: await hashPassword(uname),
emailVerified: new Date(),
completedOnboarding: opts?.completedOnboarding ?? true,
timeZone: opts?.timeZone ?? TimeZoneEnum.UK,
locale: opts?.locale ?? "en",
schedules:
opts?.completedOnboarding ?? true
? {
create: {
name: "Working Hours",
availability: {
createMany: {
data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE),
},
},
},
}
: undefined,
eventTypes: {
create: {
title: "30 min",
slug: "30-min",
length: 30,
},
},
};
};
// login using a replay of an E2E routine.
export async function login(
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "password" | "email">>,
page: Page
) {
// get locators
const loginLocator = page.locator("[data-testid=login-form]");
const emailLocator = loginLocator.locator("#email");
const passwordLocator = loginLocator.locator("#password");
const signInLocator = loginLocator.locator('[type="submit"]');
//login
await page.goto("/");
await emailLocator.fill(user.email ?? `${user.username}@example.com`);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await passwordLocator.fill(user.password ?? user.username!);
await signInLocator.click();
// 2 seconds of delay to give the session enough time for a clean load
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(2000);
}
export async function getPaymentCredential(page: Page) {
await page.goto("/apps/stripe");
/** We start the Stripe flow */
await Promise.all([
page.waitForNavigation({ url: "https://connect.stripe.com/oauth/v2/authorize?*" }),
page.click('[data-testid="install-app-button"]'),
]);
await Promise.all([
page.waitForNavigation({ url: "/apps/installed/payment?hl=stripe" }),
/** We skip filling Stripe forms (testing mode only) */
page.click('[id="skip-account-app"]'),
]);
}