fix: Provide calOrigin for organization embeds (#12380)
This commit is contained in:
parent
ada0ef242b
commit
c9b50ffb78
|
@ -1,19 +1,337 @@
|
||||||
import type { Page } from "@playwright/test";
|
import type { Page } from "@playwright/test";
|
||||||
import { expect } from "@playwright/test";
|
import { expect } from "@playwright/test";
|
||||||
|
import { Linter } from "eslint";
|
||||||
|
import { parse } from "node-html-parser";
|
||||||
|
|
||||||
|
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import { EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
import { EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
import { MembershipRole } from "@calcom/prisma/client";
|
||||||
|
|
||||||
import { test } from "./lib/fixtures";
|
import { test } from "./lib/fixtures";
|
||||||
|
|
||||||
function chooseEmbedType(page: Page, embedType: string) {
|
const linter = new Linter();
|
||||||
|
const eslintRules = {
|
||||||
|
"no-undef": "error",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
} as const;
|
||||||
|
test.describe.configure({ mode: "parallel" });
|
||||||
|
|
||||||
|
test.afterEach(({ users }) => users.deleteAll());
|
||||||
|
|
||||||
|
test.describe("Embed Code Generator Tests", () => {
|
||||||
|
test.describe("Non-Organization", () => {
|
||||||
|
test.beforeEach(async ({ users }) => {
|
||||||
|
const pro = await users.create();
|
||||||
|
await pro.apiLogin();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Event Types Page", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto("/event-types");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("open Embed Dialog and choose Inline for First Event Type", async ({ page, users }) => {
|
||||||
|
const [pro] = users.get();
|
||||||
|
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
||||||
|
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
|
||||||
|
chooseEmbedType(page, "inline");
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
embedType: "inline",
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "html",
|
||||||
|
embedType: "inline",
|
||||||
|
orgSlug: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToReactCodeTab(page);
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "react",
|
||||||
|
embedType: "inline",
|
||||||
|
orgSlug: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToPreviewTab(page);
|
||||||
|
|
||||||
|
await expectToContainValidPreviewIframe(page, {
|
||||||
|
embedType: "inline",
|
||||||
|
calLink: `${pro.username}/30-min`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("open Embed Dialog and choose floating-popup for First Event Type", async ({ page, users }) => {
|
||||||
|
const [pro] = users.get();
|
||||||
|
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
|
||||||
|
chooseEmbedType(page, "floating-popup");
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
embedType: "floating-popup",
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "html",
|
||||||
|
embedType: "floating-popup",
|
||||||
|
orgSlug: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToReactCodeTab(page);
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "react",
|
||||||
|
embedType: "floating-popup",
|
||||||
|
orgSlug: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToPreviewTab(page);
|
||||||
|
await expectToContainValidPreviewIframe(page, {
|
||||||
|
embedType: "floating-popup",
|
||||||
|
calLink: `${pro.username}/30-min`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("open Embed Dialog and choose element-click for First Event Type", async ({ page, users }) => {
|
||||||
|
const [pro] = users.get();
|
||||||
|
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
|
||||||
|
chooseEmbedType(page, "element-click");
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
embedType: "element-click",
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "html",
|
||||||
|
embedType: "element-click",
|
||||||
|
orgSlug: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToReactCodeTab(page);
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "react",
|
||||||
|
embedType: "element-click",
|
||||||
|
orgSlug: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToPreviewTab(page);
|
||||||
|
await expectToContainValidPreviewIframe(page, {
|
||||||
|
embedType: "element-click",
|
||||||
|
calLink: `${pro.username}/30-min`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test.describe("Event Type Edit Page", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto(`/event-types`);
|
||||||
|
await Promise.all([
|
||||||
|
page.locator('a[href*="/event-types/"]').first().click(),
|
||||||
|
page.waitForURL((url) => url.pathname.startsWith("/event-types/")),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("open Embed Dialog for the Event Type", async ({ page }) => {
|
||||||
|
const basePage = new URL(page.url()).pathname;
|
||||||
|
const embedUrl = await clickEmbedButton(page);
|
||||||
|
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
basePage,
|
||||||
|
});
|
||||||
|
|
||||||
|
chooseEmbedType(page, "inline");
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
basePage,
|
||||||
|
embedType: "inline",
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "html",
|
||||||
|
embedType: "inline",
|
||||||
|
orgSlug: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToPreviewTab(page);
|
||||||
|
|
||||||
|
await expectToContainValidPreviewIframe(page, {
|
||||||
|
embedType: "inline",
|
||||||
|
calLink: decodeURIComponent(embedUrl),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Organization", () => {
|
||||||
|
test.beforeEach(async ({ users, orgs }) => {
|
||||||
|
const org = await orgs.create({
|
||||||
|
name: "TestOrg",
|
||||||
|
});
|
||||||
|
const user = await users.create({
|
||||||
|
organizationId: org.id,
|
||||||
|
roleInOrganization: MembershipRole.MEMBER,
|
||||||
|
});
|
||||||
|
await user.apiLogin();
|
||||||
|
});
|
||||||
|
test.describe("Event Types Page", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto("/event-types");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("open Embed Dialog and choose Inline for First Event Type", async ({ page, users }) => {
|
||||||
|
const [user] = users.get();
|
||||||
|
const { team: org } = await user.getOrgMembership();
|
||||||
|
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
||||||
|
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
|
||||||
|
chooseEmbedType(page, "inline");
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
embedType: "inline",
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default tab is HTML code tab
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "html",
|
||||||
|
embedType: "inline",
|
||||||
|
orgSlug: org.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToReactCodeTab(page);
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "react",
|
||||||
|
embedType: "inline",
|
||||||
|
orgSlug: org.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToPreviewTab(page);
|
||||||
|
await expectToContainValidPreviewIframe(page, {
|
||||||
|
embedType: "inline",
|
||||||
|
calLink: `${user.username}/30-min`,
|
||||||
|
bookerUrl: getOrgFullOrigin(org?.slug ?? ""),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("open Embed Dialog and choose floating-popup for First Event Type", async ({ page, users }) => {
|
||||||
|
const [user] = users.get();
|
||||||
|
const { team: org } = await user.getOrgMembership();
|
||||||
|
|
||||||
|
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
|
||||||
|
chooseEmbedType(page, "floating-popup");
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
embedType: "floating-popup",
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "html",
|
||||||
|
embedType: "floating-popup",
|
||||||
|
orgSlug: org.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToReactCodeTab(page);
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "react",
|
||||||
|
embedType: "floating-popup",
|
||||||
|
orgSlug: org.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToPreviewTab(page);
|
||||||
|
await expectToContainValidPreviewIframe(page, {
|
||||||
|
embedType: "floating-popup",
|
||||||
|
calLink: `${user.username}/30-min`,
|
||||||
|
bookerUrl: getOrgFullOrigin(org?.slug ?? ""),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("open Embed Dialog and choose element-click for First Event Type", async ({ page, users }) => {
|
||||||
|
const [user] = users.get();
|
||||||
|
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
||||||
|
const { team: org } = await user.getOrgMembership();
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
|
||||||
|
chooseEmbedType(page, "element-click");
|
||||||
|
|
||||||
|
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
||||||
|
embedUrl,
|
||||||
|
embedType: "element-click",
|
||||||
|
basePage: "/event-types",
|
||||||
|
});
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "html",
|
||||||
|
embedType: "element-click",
|
||||||
|
orgSlug: org.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToReactCodeTab(page);
|
||||||
|
await expectToContainValidCode(page, {
|
||||||
|
language: "react",
|
||||||
|
embedType: "element-click",
|
||||||
|
orgSlug: org.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToPreviewTab(page);
|
||||||
|
await expectToContainValidPreviewIframe(page, {
|
||||||
|
embedType: "element-click",
|
||||||
|
calLink: `${user.username}/30-min`,
|
||||||
|
bookerUrl: getOrgFullOrigin(org?.slug ?? ""),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
type EmbedType = "inline" | "floating-popup" | "element-click";
|
||||||
|
function chooseEmbedType(page: Page, embedType: EmbedType) {
|
||||||
page.locator(`[data-testid=${embedType}]`).click();
|
page.locator(`[data-testid=${embedType}]`).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gotToPreviewTab(page: Page) {
|
async function goToPreviewTab(page: Page) {
|
||||||
// To prevent early timeouts
|
// To prevent early timeouts
|
||||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
await page.locator("[data-testid=embed-tabs]").locator("text=Preview").click();
|
await page.locator("[data-testid=horizontal-tab-Preview]").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function goToReactCodeTab(page: Page) {
|
||||||
|
// To prevent early timeouts
|
||||||
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await page.locator("[data-testid=horizontal-tab-React]").click();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickEmbedButton(page: Page) {
|
async function clickEmbedButton(page: Page) {
|
||||||
|
@ -55,7 +373,7 @@ async function expectToBeNavigatingToEmbedCodeAndPreviewDialog(
|
||||||
basePage,
|
basePage,
|
||||||
}: {
|
}: {
|
||||||
embedUrl: string | null;
|
embedUrl: string | null;
|
||||||
embedType: string;
|
embedType: EmbedType;
|
||||||
basePage: string;
|
basePage: string;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -73,10 +391,108 @@ async function expectToBeNavigatingToEmbedCodeAndPreviewDialog(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function expectToContainValidCode(page: Page, { embedType }: { embedType: string }) {
|
async function expectToContainValidCode(
|
||||||
|
page: Page,
|
||||||
|
{
|
||||||
|
embedType,
|
||||||
|
language,
|
||||||
|
orgSlug,
|
||||||
|
}: { embedType: EmbedType; language: "html" | "react"; orgSlug: string | null }
|
||||||
|
) {
|
||||||
|
if (language === "react") {
|
||||||
|
return expectValidReactEmbedSnippet(page, { embedType, orgSlug });
|
||||||
|
}
|
||||||
|
if (language === "html") {
|
||||||
|
return expectValidHtmlEmbedSnippet(page, { embedType, orgSlug });
|
||||||
|
}
|
||||||
|
throw new Error("Unknown language");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectValidHtmlEmbedSnippet(
|
||||||
|
page: Page,
|
||||||
|
{ embedType, orgSlug }: { embedType: EmbedType; orgSlug: string | null }
|
||||||
|
) {
|
||||||
const embedCode = await page.locator("[data-testid=embed-code]").inputValue();
|
const embedCode = await page.locator("[data-testid=embed-code]").inputValue();
|
||||||
expect(embedCode.includes("(function (C, A, L)")).toBe(true);
|
expect(embedCode).toContain("function (C, A, L)");
|
||||||
expect(embedCode.includes(`Cal ${embedType} embed code begins`)).toBe(true);
|
expect(embedCode).toContain(`Cal ${embedType} embed code begins`);
|
||||||
|
if (orgSlug) {
|
||||||
|
expect(embedCode).toContain(orgSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dom = parse(embedCode);
|
||||||
|
const scripts = dom.getElementsByTagName("script");
|
||||||
|
assertThatCodeIsValidVanillaJsCode(scripts[0].innerText);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: () => `passed`,
|
||||||
|
pass: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertThatCodeIsValidVanillaJsCode(code: string) {
|
||||||
|
const lintResult = linter.verify(code, {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2021,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
Cal: "readonly",
|
||||||
|
},
|
||||||
|
rules: eslintRules,
|
||||||
|
});
|
||||||
|
if (lintResult.length) {
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
lintResult,
|
||||||
|
code,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expect(lintResult.length).toBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertThatCodeIsValidReactCode(code: string) {
|
||||||
|
const lintResult = linter.verify(code, {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2021,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
rules: eslintRules,
|
||||||
|
});
|
||||||
|
if (lintResult.length) {
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
lintResult,
|
||||||
|
code,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expect(lintResult.length).toBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectValidReactEmbedSnippet(
|
||||||
|
page: Page,
|
||||||
|
{ embedType, orgSlug }: { embedType: EmbedType; orgSlug: string | null }
|
||||||
|
) {
|
||||||
|
const embedCode = await page.locator("[data-testid=embed-react]").inputValue();
|
||||||
|
expect(embedCode).toContain("export default function MyApp(");
|
||||||
|
expect(embedCode).toContain(
|
||||||
|
embedType === "floating-popup" ? "floatingButton" : embedType === "inline" ? `<Cal` : "data-cal-link"
|
||||||
|
);
|
||||||
|
if (orgSlug) {
|
||||||
|
expect(embedCode).toContain(orgSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThatCodeIsValidReactCode(embedCode);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: () => `passed`,
|
message: () => `passed`,
|
||||||
pass: true,
|
pass: true,
|
||||||
|
@ -88,141 +504,10 @@ async function expectToContainValidCode(page: Page, { embedType }: { embedType:
|
||||||
*/
|
*/
|
||||||
async function expectToContainValidPreviewIframe(
|
async function expectToContainValidPreviewIframe(
|
||||||
page: Page,
|
page: Page,
|
||||||
{ embedType, calLink }: { embedType: string; calLink: string }
|
{ embedType, calLink, bookerUrl }: { embedType: EmbedType; calLink: string; bookerUrl?: string }
|
||||||
) {
|
) {
|
||||||
const bookerUrl = `${WEBAPP_URL}`;
|
bookerUrl = bookerUrl || `${WEBAPP_URL}`;
|
||||||
expect(await page.locator("[data-testid=embed-preview]").getAttribute("src")).toContain(
|
expect(await page.locator("[data-testid=embed-preview]").getAttribute("src")).toContain(
|
||||||
`/preview.html?embedType=${embedType}&calLink=${calLink}&embedLibUrl=${EMBED_LIB_URL}&bookerUrl=${bookerUrl}`
|
`/preview.html?embedType=${embedType}&calLink=${calLink}&embedLibUrl=${EMBED_LIB_URL}&bookerUrl=${bookerUrl}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe.configure({ mode: "parallel" });
|
|
||||||
|
|
||||||
test.afterEach(({ users }) => users.deleteAll());
|
|
||||||
|
|
||||||
test.describe("Embed Code Generator Tests", () => {
|
|
||||||
test.beforeEach(async ({ users }) => {
|
|
||||||
const pro = await users.create();
|
|
||||||
await pro.apiLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Event Types Page", () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.goto("/event-types");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("open Embed Dialog and choose Inline for First Event Type", async ({ page, users }) => {
|
|
||||||
const [pro] = users.get();
|
|
||||||
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
|
||||||
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
|
||||||
embedUrl,
|
|
||||||
basePage: "/event-types",
|
|
||||||
});
|
|
||||||
|
|
||||||
chooseEmbedType(page, "inline");
|
|
||||||
|
|
||||||
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
|
||||||
embedUrl,
|
|
||||||
embedType: "inline",
|
|
||||||
basePage: "/event-types",
|
|
||||||
});
|
|
||||||
|
|
||||||
await expectToContainValidCode(page, { embedType: "inline" });
|
|
||||||
|
|
||||||
await gotToPreviewTab(page);
|
|
||||||
|
|
||||||
await expectToContainValidPreviewIframe(page, {
|
|
||||||
embedType: "inline",
|
|
||||||
calLink: `${pro.username}/30-min`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("open Embed Dialog and choose floating-popup for First Event Type", async ({ page, users }) => {
|
|
||||||
const [pro] = users.get();
|
|
||||||
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
|
||||||
|
|
||||||
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
|
||||||
embedUrl,
|
|
||||||
basePage: "/event-types",
|
|
||||||
});
|
|
||||||
|
|
||||||
chooseEmbedType(page, "floating-popup");
|
|
||||||
|
|
||||||
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
|
||||||
embedUrl,
|
|
||||||
embedType: "floating-popup",
|
|
||||||
basePage: "/event-types",
|
|
||||||
});
|
|
||||||
await expectToContainValidCode(page, { embedType: "floating-popup" });
|
|
||||||
|
|
||||||
await gotToPreviewTab(page);
|
|
||||||
await expectToContainValidPreviewIframe(page, {
|
|
||||||
embedType: "floating-popup",
|
|
||||||
calLink: `${pro.username}/30-min`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("open Embed Dialog and choose element-click for First Event Type", async ({ page, users }) => {
|
|
||||||
const [pro] = users.get();
|
|
||||||
const embedUrl = await clickFirstEventTypeEmbedButton(page);
|
|
||||||
|
|
||||||
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
|
||||||
embedUrl,
|
|
||||||
basePage: "/event-types",
|
|
||||||
});
|
|
||||||
|
|
||||||
chooseEmbedType(page, "element-click");
|
|
||||||
|
|
||||||
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
|
||||||
embedUrl,
|
|
||||||
embedType: "element-click",
|
|
||||||
basePage: "/event-types",
|
|
||||||
});
|
|
||||||
await expectToContainValidCode(page, { embedType: "element-click" });
|
|
||||||
|
|
||||||
await gotToPreviewTab(page);
|
|
||||||
await expectToContainValidPreviewIframe(page, {
|
|
||||||
embedType: "element-click",
|
|
||||||
calLink: `${pro.username}/30-min`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Event Type Edit Page", () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.goto(`/event-types`);
|
|
||||||
await Promise.all([
|
|
||||||
page.locator('a[href*="/event-types/"]').first().click(),
|
|
||||||
page.waitForURL((url) => url.pathname.startsWith("/event-types/")),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("open Embed Dialog for the Event Type", async ({ page }) => {
|
|
||||||
const basePage = new URL(page.url()).pathname;
|
|
||||||
const embedUrl = await clickEmbedButton(page);
|
|
||||||
await expectToBeNavigatingToEmbedTypesDialog(page, {
|
|
||||||
embedUrl,
|
|
||||||
basePage,
|
|
||||||
});
|
|
||||||
|
|
||||||
chooseEmbedType(page, "inline");
|
|
||||||
|
|
||||||
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
|
|
||||||
embedUrl,
|
|
||||||
basePage,
|
|
||||||
embedType: "inline",
|
|
||||||
});
|
|
||||||
|
|
||||||
await expectToContainValidCode(page, {
|
|
||||||
embedType: "inline",
|
|
||||||
});
|
|
||||||
|
|
||||||
await gotToPreviewTab(page);
|
|
||||||
|
|
||||||
await expectToContainValidPreviewIframe(page, {
|
|
||||||
embedType: "inline",
|
|
||||||
calLink: decodeURIComponent(embedUrl),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -460,7 +460,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
|
||||||
}
|
}
|
||||||
return membership;
|
return membership;
|
||||||
},
|
},
|
||||||
getOrg: async () => {
|
getOrgMembership: async () => {
|
||||||
return prisma.membership.findFirstOrThrow({
|
return prisma.membership.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -471,7 +471,13 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: { team: { select: { children: true, metadata: true, name: true } } },
|
include: {
|
||||||
|
team: {
|
||||||
|
include: {
|
||||||
|
children: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getFirstEventAsOwner: async () =>
|
getFirstEventAsOwner: async () =>
|
||||||
|
|
|
@ -168,7 +168,7 @@ test.describe("Teams - NonOrg", () => {
|
||||||
await expect(page.locator("[data-testid=alert]")).toBeVisible();
|
await expect(page.locator("[data-testid=alert]")).toBeVisible();
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
const org = await owner.getOrg();
|
const org = await owner.getOrgMembership();
|
||||||
await prisma.team.delete({ where: { id: org.teamId } });
|
await prisma.team.delete({ where: { id: org.teamId } });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,7 @@ test.describe("Unpublished", () => {
|
||||||
|
|
||||||
test("Organization profile", async ({ users, page }) => {
|
test("Organization profile", async ({ users, page }) => {
|
||||||
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
|
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
|
||||||
const { team: org } = await owner.getOrg();
|
const { team: org } = await owner.getOrgMembership();
|
||||||
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
||||||
await page.goto(`/org/${requestedSlug}`);
|
await page.goto(`/org/${requestedSlug}`);
|
||||||
await page.waitForLoadState("networkidle");
|
await page.waitForLoadState("networkidle");
|
||||||
|
@ -62,7 +62,7 @@ test.describe("Unpublished", () => {
|
||||||
isOrg: true,
|
isOrg: true,
|
||||||
hasSubteam: true,
|
hasSubteam: true,
|
||||||
});
|
});
|
||||||
const { team: org } = await owner.getOrg();
|
const { team: org } = await owner.getOrgMembership();
|
||||||
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
||||||
const [{ slug: subteamSlug }] = org.children as { slug: string }[];
|
const [{ slug: subteamSlug }] = org.children as { slug: string }[];
|
||||||
await page.goto(`/org/${requestedSlug}/team/${subteamSlug}`);
|
await page.goto(`/org/${requestedSlug}/team/${subteamSlug}`);
|
||||||
|
@ -80,7 +80,7 @@ test.describe("Unpublished", () => {
|
||||||
isOrg: true,
|
isOrg: true,
|
||||||
hasSubteam: true,
|
hasSubteam: true,
|
||||||
});
|
});
|
||||||
const { team: org } = await owner.getOrg();
|
const { team: org } = await owner.getOrgMembership();
|
||||||
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
||||||
const [{ slug: subteamSlug, id: subteamId }] = org.children as { slug: string; id: number }[];
|
const [{ slug: subteamSlug, id: subteamId }] = org.children as { slug: string; id: number }[];
|
||||||
const { slug: subteamEventSlug } = await owner.getFirstTeamEvent(subteamId);
|
const { slug: subteamEventSlug } = await owner.getFirstTeamEvent(subteamId);
|
||||||
|
@ -95,7 +95,7 @@ test.describe("Unpublished", () => {
|
||||||
|
|
||||||
test("Organization user", async ({ users, page }) => {
|
test("Organization user", async ({ users, page }) => {
|
||||||
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
|
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
|
||||||
const { team: org } = await owner.getOrg();
|
const { team: org } = await owner.getOrgMembership();
|
||||||
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
||||||
await page.goto(`/org/${requestedSlug}/${owner.username}`);
|
await page.goto(`/org/${requestedSlug}/${owner.username}`);
|
||||||
await page.waitForLoadState("networkidle");
|
await page.waitForLoadState("networkidle");
|
||||||
|
@ -107,7 +107,7 @@ test.describe("Unpublished", () => {
|
||||||
|
|
||||||
test("Organization user event-type", async ({ users, page }) => {
|
test("Organization user event-type", async ({ users, page }) => {
|
||||||
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
|
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
|
||||||
const { team: org } = await owner.getOrg();
|
const { team: org } = await owner.getOrgMembership();
|
||||||
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
const { requestedSlug } = org.metadata as { requestedSlug: string };
|
||||||
const [{ slug: ownerEventType }] = owner.eventTypes;
|
const [{ slug: ownerEventType }] = owner.eventTypes;
|
||||||
await page.goto(`/org/${requestedSlug}/${owner.username}/${ownerEventType}`);
|
await page.goto(`/org/${requestedSlug}/${owner.username}/${ownerEventType}`);
|
||||||
|
|
|
@ -104,10 +104,9 @@ function generateFiles() {
|
||||||
* If a file has index.ts or index.tsx, it can be imported after removing the index.ts* part
|
* If a file has index.ts or index.tsx, it can be imported after removing the index.ts* part
|
||||||
*/
|
*/
|
||||||
function getModulePath(path: string, moduleName: string) {
|
function getModulePath(path: string, moduleName: string) {
|
||||||
return (
|
return `./${path.replace(/\\/g, "/")}/${moduleName
|
||||||
`./${path.replace(/\\/g, "/")}/` +
|
.replace(/\/index\.ts|\/index\.tsx/, "")
|
||||||
moduleName.replace(/\/index\.ts|\/index\.tsx/, "").replace(/\.tsx$|\.ts$/, "")
|
.replace(/\.tsx$|\.ts$/, "")}`;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImportConfig =
|
type ImportConfig =
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
|
import { CAL_URL, IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import type { PreviewState } from "../types";
|
import type { PreviewState } from "../types";
|
||||||
import { embedLibUrl } from "./constants";
|
import { embedLibUrl } from "./constants";
|
||||||
import { getDimension } from "./getDimension";
|
import { getDimension } from "./getDimension";
|
||||||
|
|
||||||
|
export const doWeNeedCalOriginProp = (embedCalOrigin: string) => {
|
||||||
|
// If we are self hosted, calOrigin won't be app.cal.com so we need to pass it
|
||||||
|
// If we are not self hosted but it's still different from WEBAPP_URL and CAL_URL, we need to pass it -> It happens for organization booking URL at the moment
|
||||||
|
return IS_SELF_HOSTED || (embedCalOrigin !== WEBAPP_URL && embedCalOrigin !== CAL_URL);
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const Codes = {
|
export const Codes = {
|
||||||
react: {
|
react: {
|
||||||
|
@ -33,13 +39,9 @@ export const Codes = {
|
||||||
return <Cal
|
return <Cal
|
||||||
calLink="${calLink}"
|
calLink="${calLink}"
|
||||||
style={{width:"${width}",height:"${height}",overflow:"scroll"}}
|
style={{width:"${width}",height:"${height}",overflow:"scroll"}}
|
||||||
${previewState.layout ? `config={{layout: '${previewState.layout}'}}` : ""}${
|
${previewState.layout ? `config={{layout: '${previewState.layout}'}}` : ""}
|
||||||
IS_SELF_HOSTED
|
${doWeNeedCalOriginProp(embedCalOrigin) ? ` calOrigin="${embedCalOrigin}"` : ""}
|
||||||
? `
|
${IS_SELF_HOSTED ? `calJsUrl="${embedLibUrl}"` : ""}
|
||||||
calOrigin="${embedCalOrigin}"
|
|
||||||
calJsUrl="${embedLibUrl}"`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
/>;
|
/>;
|
||||||
};`;
|
};`;
|
||||||
},
|
},
|
||||||
|
@ -53,7 +55,7 @@ export const Codes = {
|
||||||
return code`
|
return code`
|
||||||
import { getCalApi } from "@calcom/embed-react";
|
import { getCalApi } from "@calcom/embed-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
export default function App() {
|
export default function MyApp() {
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
(async function () {
|
(async function () {
|
||||||
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${embedLibUrl}"` : ""});
|
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${embedLibUrl}"` : ""});
|
||||||
|
@ -77,7 +79,7 @@ export const Codes = {
|
||||||
return code`
|
return code`
|
||||||
import { getCalApi } from "@calcom/embed-react";
|
import { getCalApi } from "@calcom/embed-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
export default function App() {
|
export default function MyApp() {
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
(async function () {
|
(async function () {
|
||||||
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${embedLibUrl}"` : ""});
|
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${embedLibUrl}"` : ""});
|
||||||
|
@ -85,7 +87,8 @@ export const Codes = {
|
||||||
})();
|
})();
|
||||||
}, [])
|
}, [])
|
||||||
return <button
|
return <button
|
||||||
data-cal-link="${calLink}"${IS_SELF_HOSTED ? `\ndata-cal-origin="${embedCalOrigin}"` : ""}
|
data-cal-link="${calLink}"
|
||||||
|
${doWeNeedCalOriginProp(embedCalOrigin) ? ` data-cal-origin="${embedCalOrigin}"` : ""}
|
||||||
${`data-cal-config='${JSON.stringify({
|
${`data-cal-config='${JSON.stringify({
|
||||||
layout: previewState.layout,
|
layout: previewState.layout,
|
||||||
})}'`}
|
})}'`}
|
||||||
|
|
|
@ -2,14 +2,14 @@ import { forwardRef } from "react";
|
||||||
import type { MutableRefObject } from "react";
|
import type { MutableRefObject } from "react";
|
||||||
|
|
||||||
import type { BookerLayout } from "@calcom/features/bookings/Booker/types";
|
import type { BookerLayout } from "@calcom/features/bookings/Booker/types";
|
||||||
import { APP_NAME, IS_SELF_HOSTED } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
|
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { TextArea } from "@calcom/ui";
|
import { TextArea } from "@calcom/ui";
|
||||||
import { Code, Trello } from "@calcom/ui/components/icon";
|
import { Code, Trello } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import type { EmbedType, PreviewState, EmbedFramework } from "../types";
|
import type { EmbedType, PreviewState, EmbedFramework } from "../types";
|
||||||
import { Codes } from "./EmbedCodes";
|
import { Codes, doWeNeedCalOriginProp } from "./EmbedCodes";
|
||||||
import { EMBED_PREVIEW_HTML_URL, embedLibUrl } from "./constants";
|
import { EMBED_PREVIEW_HTML_URL, embedLibUrl } from "./constants";
|
||||||
import { getDimension } from "./getDimension";
|
import { getDimension } from "./getDimension";
|
||||||
import { useEmbedCalOrigin } from "./hooks";
|
import { useEmbedCalOrigin } from "./hooks";
|
||||||
|
@ -193,7 +193,7 @@ const getEmbedTypeSpecificString = ({
|
||||||
} else if (embedType === "floating-popup") {
|
} else if (embedType === "floating-popup") {
|
||||||
const floatingButtonArg = {
|
const floatingButtonArg = {
|
||||||
calLink,
|
calLink,
|
||||||
...(IS_SELF_HOSTED ? { calOrigin: embedCalOrigin } : null),
|
...(doWeNeedCalOriginProp(embedCalOrigin) ? { calOrigin: embedCalOrigin } : null),
|
||||||
...previewState.floatingPopup,
|
...previewState.floatingPopup,
|
||||||
};
|
};
|
||||||
return frameworkCodes[embedType]({
|
return frameworkCodes[embedType]({
|
||||||
|
|
Loading…
Reference in New Issue
Block a user