8c39210a12
* chore: add new view for two step slot selection for embed * fix: make sure two step slot selection is false by default * chore: update embed playground to showcase two step slot selection * chore: add tests * chore: update embed snippet generator code to include two step slot selection * chore: update docs * fix: back button styling * chore: implement feedback from cubic * fix: add missing isEnableTwoStepSlotSelectionVisible properties to test mock store Co-Authored-By: unknown <> * chore: implement PR feedback * chore: update slot selection modal * chore: add prop to hide available times header * fix: scope two-step slot selection visibility to mobile only Restores the isMobile check in the timeslot visibility guard to ensure desktop embeds continue to show the booking UI when two-step slot selection is enabled. The feature should only hide the timeslot list on mobile devices. Addresses Cubic AI review feedback (confidence 9/10). Co-Authored-By: unknown <> * fixup: use translations for loading instead of actual word * fix: type check * fix: add window.matchMedia mock for jsdom test environment Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: add window.matchMedia mock to packages/testing/src/setupVitest.ts Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: unit tests failing * add a width style to the two-step slot selection embed container. * refactor: rename enableTwoStepSlotSelection to useSlotsViewOnSmallScreen - Rename external-facing embed config option from enableTwoStepSlotSelection to useSlotsViewOnSmallScreen - Update URL parameter parsing in embed-iframe.ts - Update type definitions in types.ts and index.d.ts - Update internal variable names to slotsViewOnSmallScreen for consistency - Update documentation, playground examples, and tests Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Add config to prefill all booking fields so that slot has confirm button along with it * chore: implement PR feedback * fix: move twoStepSlotSelection embed outside misc-embeds div to fix e2e test The e2e test was failing because the misc-embeds div content was intercepting pointer events on mobile viewport. This follows the same pattern used for skeletonDemo - the embed is now outside misc-embeds and the div is hidden when testing this namespace. Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: prevent pointer event interception in twoStepSlotSelection embed Hide heading and note elements and set pointer-events: none on container elements while keeping pointer-events: auto on the iframe container. This prevents the container from intercepting clicks on the iframe during mobile viewport e2e tests. Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: hide all non-essential content for twoStepSlotSelection e2e test Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: disable pointer-events on body for twoStepSlotSelection e2e test Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: remove pointer-events: auto on container to let clicks pass through to iframe Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: explicitly set pointer-events: none on container and inline-embed-container Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: also set pointer-events: none on html element to prevent interception Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: make .place div fill viewport for twoStepSlotSelection e2e test Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: failing embed tests * fixup * fixup fixup * fix: failing tests * fix: update checks for verifying prefilled date --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
176 lines
6.1 KiB
TypeScript
176 lines
6.1 KiB
TypeScript
import { expect } from "@playwright/test";
|
|
|
|
// eslint-disable-next-line no-restricted-imports
|
|
import { test } from "@calcom/web/playwright/lib/fixtures";
|
|
|
|
import "../../src/types";
|
|
|
|
test.describe("Embed Pages", () => {
|
|
test("Event Type Page: should not have margin top on embed page", async ({ page }) => {
|
|
await page.goto("http://localhost:3000/free/30min/embed");
|
|
// Checks the margin from top by checking the distance between the div inside main from the viewport
|
|
const marginFromTop = await page.evaluate(async () => {
|
|
return await new Promise<{
|
|
bookerContainer: number;
|
|
mainEl: number;
|
|
}>((resolve) => {
|
|
(function tryGettingBoundingRect() {
|
|
const mainElement = document.querySelector(".main");
|
|
const bookerContainer = document.querySelector('[data-testid="booker-container"]');
|
|
|
|
if (mainElement && bookerContainer) {
|
|
// This returns the distance of the div element from the viewport
|
|
const mainElBoundingRect = mainElement.getBoundingClientRect();
|
|
const bookerContainerBoundingRect = bookerContainer.getBoundingClientRect();
|
|
resolve({ bookerContainer: bookerContainerBoundingRect.top, mainEl: mainElBoundingRect.top });
|
|
} else {
|
|
setTimeout(tryGettingBoundingRect, 500);
|
|
}
|
|
})();
|
|
});
|
|
});
|
|
|
|
expect(marginFromTop.bookerContainer).toBe(0);
|
|
expect(marginFromTop.mainEl).toBe(0);
|
|
});
|
|
|
|
test("Event Type Page: should have margin top on non embed page", async ({ page }) => {
|
|
await page.goto("http://localhost:3000/free/30min");
|
|
|
|
// Checks the margin from top by checking the distance between the div inside main from the viewport
|
|
const marginFromTop = await page.evaluate(() => {
|
|
const mainElement = document.querySelector("main");
|
|
const divElement = mainElement?.querySelector("div");
|
|
|
|
if (mainElement && divElement) {
|
|
// This returns the distance of the div element from the viewport
|
|
const divRect = divElement.getBoundingClientRect();
|
|
return divRect.top;
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
expect(marginFromTop).not.toBe(0);
|
|
});
|
|
|
|
test.describe("isEmbed, getEmbedNamespace, getEmbedTheme testing", () => {
|
|
test("when `window.name` is set to 'cal-embed=' and `theme` is supplied as a query param", async ({
|
|
page,
|
|
}) => {
|
|
const queryParamTheme = "dark";
|
|
await page.evaluate(() => {
|
|
window.name = "cal-embed=";
|
|
});
|
|
|
|
await page.goto(`http://localhost:3000/free/30min?theme=${queryParamTheme}`);
|
|
|
|
const isEmbed = await page.evaluate(() => {
|
|
return window?.isEmbed?.();
|
|
});
|
|
|
|
const embedNamespace = await page.evaluate(() => {
|
|
return window?.getEmbedNamespace?.();
|
|
});
|
|
|
|
expect(embedNamespace).toBe("");
|
|
expect(isEmbed).toBe(true);
|
|
const embedTheme = await page.evaluate(() => {
|
|
return window?.getEmbedTheme?.();
|
|
});
|
|
expect(embedTheme).toBe(queryParamTheme);
|
|
const embedStoreTheme = await page.evaluate(() => {
|
|
return window.CalEmbed.embedStore.theme;
|
|
});
|
|
// Verify that the theme is set on embedStore.
|
|
expect(embedStoreTheme).toBe(queryParamTheme);
|
|
});
|
|
|
|
test("when `window.name` does not contain `cal-embed=`", async ({ page }) => {
|
|
await page.evaluate(() => {
|
|
window.name = "testing";
|
|
});
|
|
await page.goto(`http://localhost:3000/free/30min`);
|
|
const isEmbed = await page.evaluate(() => {
|
|
return window?.isEmbed?.();
|
|
});
|
|
const embedNamespace = await page.evaluate(() => {
|
|
return window?.getEmbedNamespace?.();
|
|
});
|
|
expect(isEmbed).toBe(false);
|
|
expect(embedNamespace).toBe(null);
|
|
});
|
|
|
|
test("`getEmbedTheme` should use `window.CalEmbed.embedStore.theme` instead of `theme` query param if set", async ({
|
|
page,
|
|
}) => {
|
|
const theme = "dark";
|
|
await page.evaluate(() => {
|
|
window.name = "cal-embed=";
|
|
});
|
|
await page.goto("http://localhost:3000/free/30min?theme=dark");
|
|
let embedTheme = await page.evaluate(() => {
|
|
return window?.getEmbedTheme?.();
|
|
});
|
|
expect(embedTheme).toBe(theme);
|
|
|
|
// Fake a scenario where theme query param is lost during navigation
|
|
await page.evaluate(() => {
|
|
history.pushState({}, "", "/free/30min");
|
|
});
|
|
|
|
embedTheme = await page.evaluate(() => {
|
|
return window?.getEmbedTheme?.();
|
|
});
|
|
|
|
// Theme should still remain same as it's read from `window.CalEmbed.embedStore.theme` which is updated by getEmbedTheme itself
|
|
expect(embedTheme).toBe(theme);
|
|
});
|
|
});
|
|
|
|
test.describe("useSlotsViewOnSmallScreen", () => {
|
|
test("should enable slots view on small screen when parameter is 'true'", async ({
|
|
page,
|
|
}) => {
|
|
await page.evaluate(() => {
|
|
window.name = "cal-embed=";
|
|
});
|
|
await page.goto("http://localhost:3000/free/30min/embed?useSlotsViewOnSmallScreen=true");
|
|
|
|
const useSlotsViewOnSmallScreen = await page.evaluate(() => {
|
|
return window.CalEmbed?.embedStore?.uiConfig?.useSlotsViewOnSmallScreen;
|
|
});
|
|
|
|
expect(useSlotsViewOnSmallScreen).toBe(true);
|
|
});
|
|
|
|
test("should default to false when parameter is not present", async ({
|
|
page,
|
|
}) => {
|
|
await page.evaluate(() => {
|
|
window.name = "cal-embed=";
|
|
});
|
|
await page.goto("http://localhost:3000/free/30min/embed");
|
|
|
|
const useSlotsViewOnSmallScreen = await page.evaluate(() => {
|
|
return window.CalEmbed?.embedStore?.uiConfig?.useSlotsViewOnSmallScreen;
|
|
});
|
|
|
|
expect(useSlotsViewOnSmallScreen).toBe(false);
|
|
});
|
|
|
|
test("should be false when parameter is 'false'", async ({ page }) => {
|
|
await page.evaluate(() => {
|
|
window.name = "cal-embed=";
|
|
});
|
|
await page.goto("http://localhost:3000/free/30min/embed?useSlotsViewOnSmallScreen=false");
|
|
|
|
const useSlotsViewOnSmallScreen = await page.evaluate(() => {
|
|
return window.CalEmbed?.embedStore?.uiConfig?.useSlotsViewOnSmallScreen;
|
|
});
|
|
|
|
expect(useSlotsViewOnSmallScreen).toBe(false);
|
|
});
|
|
});
|
|
});
|