Embed: Documentation TS errors Fix, IFrame Communication Tests, Updated documentation (#2432)
This commit is contained in:
parent
eceba51020
commit
df4a41127f
10 changed files with 132 additions and 14 deletions
|
@ -7,6 +7,7 @@
|
|||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
||||
"dev": "PORT=4000 next",
|
||||
"lint": "next lint",
|
||||
"type-check": "tsc --pretty --noEmit",
|
||||
"lint:report": "eslint . --format json --output-file ../../lint-results/docs.json",
|
||||
"start": "PORT=4000 next start",
|
||||
"build": "next build"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { AppProps } from "next/app";
|
||||
import "nextra-theme-docs/style.css";
|
||||
|
||||
import "./style.css";
|
||||
|
||||
export default function Nextra({ Component, pageProps }) {
|
||||
export default function Nextra({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
|
|
@ -178,3 +178,31 @@ Cal("preload", { calLink });
|
|||
```
|
||||
|
||||
- `calLink` - Cal Link that you want to embed e.g. john. Just give the username. No need to give the full URL [https://cal.com/john]()
|
||||
|
||||
## Actions
|
||||
You can listen to an action that occurs in embedded cal link as follows. You can think of them as DOM events. We are avoiding the term events to not confuse it with Cal Events.
|
||||
```javascript
|
||||
Cal("on", {
|
||||
action: "ANY_ACTION_NAME",
|
||||
callback: (e)=>{
|
||||
// `data` is properties for the event.
|
||||
// `type` is the name of the action(You can also call it type of the action.) This would be same as "ANY_ACTION_NAME" except when ANY_ACTION_NAME="*" which listens to all the events.
|
||||
// `namespace` tells you the Cal namespace for which the event is fired/
|
||||
const {data, type, namespace} = e.detail;
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Following are the list of supported actions.
|
||||
-
|
||||
| action | description | properties |
|
||||
|----------------------|------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| eventTypeSelected | When user chooses an event-type from the listing. | eventType:object // Event Type that has been selected" |
|
||||
| bookingSuccessful | When the booking is successfully done. It might not be confirmed. | confirmed: boolean; //Whether confirmation from organizer is pending or not <br/><br/>eventType: "Object for Event Type that has been booked"; <br/><br/>date: string; // Date of Event <br/><br/>duration: number; //Duration of booked Event <br/><br/>organizer: object //Organizer details like name, timezone, email |
|
||||
| linkReady | Tells that the link is ready to be shown now. | None |
|
||||
| linkFailed | Fired if link fails to load | code: number; // Error Code <br/><br/>msg: string; //Human Readable msg <br/><br/>data: object // More details to debug the error |
|
||||
| __iframeReady | It is fired when the embedded iframe is ready to communicate with parent snippet. This is mostly for internal use by Embed Snippet | None |
|
||||
| __windowLoadComplete | Tells that window load for iframe is complete | None |
|
||||
| __dimensionChanged | Tells that dimensions of the content inside the iframe changed. | iframeWidth:number, iframeHeight:number |
|
||||
|
||||
_Actions that start with __ are internal._
|
|
@ -40,6 +40,7 @@ Make `dist/embed.umd.js` servable on URL <http://cal.com/embed.js>
|
|||
- Accessibility and UI/UX Issues
|
||||
- let user choose the loader for ModalBox
|
||||
- If website owner links the booking page directly for an event, should the user be able to go to events-listing page using back button ?
|
||||
- Let user specify both dark and light theme colors. Right now the colors specified are for light theme.
|
||||
|
||||
- Automation Tests
|
||||
- Run automation tests in CI
|
||||
|
|
|
@ -60,13 +60,23 @@ declare global {
|
|||
namespace PlaywrightTest {
|
||||
//FIXME: how to restrict it to Frame only
|
||||
interface Matchers<R> {
|
||||
toBeEmbedCalLink(expectedUrlDetails?: ExpectedUrlDetails): R;
|
||||
toBeEmbedCalLink(
|
||||
calNamespace: string,
|
||||
getActionFiredDetails: Function,
|
||||
expectedUrlDetails?: ExpectedUrlDetails
|
||||
): R;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
async toBeEmbedCalLink(iframe: Frame, expectedUrlDetails: ExpectedUrlDetails = {}) {
|
||||
async toBeEmbedCalLink(
|
||||
iframe: Frame,
|
||||
calNamespace: string,
|
||||
//TODO: Move it to testUtil, so that it doesn't need to be passed
|
||||
getActionFiredDetails: Function,
|
||||
expectedUrlDetails: ExpectedUrlDetails = {}
|
||||
) {
|
||||
if (!iframe || !iframe.url) {
|
||||
return {
|
||||
pass: false,
|
||||
|
@ -112,6 +122,19 @@ expect.extend({
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
const iframeReadyEventDetail = await getActionFiredDetails({
|
||||
calNamespace,
|
||||
actionType: "__iframeReady",
|
||||
});
|
||||
|
||||
if (!iframeReadyEventDetail) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Iframe not ready to communicate with parent`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `passed`,
|
||||
|
|
|
@ -1,3 +1,53 @@
|
|||
import { test as base } from "@playwright/test";
|
||||
import { test as base, Page } from "@playwright/test";
|
||||
|
||||
export const test = base.extend({});
|
||||
interface Fixtures {
|
||||
addEmbedListeners: (calNamespace: string) => Promise<void>;
|
||||
getActionFiredDetails: (a: { calNamespace: string; actionType: string }) => Promise<any>;
|
||||
}
|
||||
export const test = base.extend<Fixtures>({
|
||||
addEmbedListeners: async ({ page }: { page: Page }, use) => {
|
||||
await use(async (calNamespace: string) => {
|
||||
await page.addInitScript(
|
||||
({ calNamespace }: { calNamespace: string }) => {
|
||||
//@ts-ignore
|
||||
window.eventsFiredStoreForPlaywright = window.eventsFiredStoreForPlaywright || {};
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (parent !== window) {
|
||||
// Firefox seems to execute this snippet for iframe as well. Avoid that. It must be executed only for parent frame.
|
||||
return;
|
||||
}
|
||||
console.log("PlaywrightTest:", "Adding listener for __iframeReady");
|
||||
//@ts-ignore
|
||||
let api = window.Cal;
|
||||
if (calNamespace) {
|
||||
//@ts-ignore
|
||||
api = window.Cal.ns[calNamespace];
|
||||
}
|
||||
api("on", {
|
||||
action: "*",
|
||||
callback: (e: any) => {
|
||||
//@ts-ignore
|
||||
const store = window.eventsFiredStoreForPlaywright;
|
||||
let eventStore = (store[`${e.detail.type}-${e.detail.namespace}`] =
|
||||
store[`${e.detail.type}-${e.detail.namespace}`] || []);
|
||||
eventStore.push(e.detail);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
{ calNamespace }
|
||||
);
|
||||
});
|
||||
},
|
||||
getActionFiredDetails: async ({ page }, use) => {
|
||||
await use(async ({ calNamespace, actionType }) => {
|
||||
return await page.evaluate(
|
||||
({ actionType, calNamespace }) => {
|
||||
//@ts-ignore
|
||||
return window.eventsFiredStoreForPlaywright[`${actionType}-${calNamespace}`];
|
||||
},
|
||||
{ actionType, calNamespace }
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,7 +3,9 @@ import { expect } from "@playwright/test";
|
|||
import { test } from "../fixtures/fixtures";
|
||||
import { todo, getEmbedIframe } from "../lib/testUtils";
|
||||
|
||||
test("should open embed iframe on click", async ({ page }) => {
|
||||
test("should open embed iframe on click", async ({ page, addEmbedListeners, getActionFiredDetails }) => {
|
||||
const calNamespace = "prerendertestLightTheme";
|
||||
await addEmbedListeners(calNamespace);
|
||||
await page.goto("/?only=prerender-test");
|
||||
let embedIframe = await getEmbedIframe({ page, pathname: "/free" });
|
||||
expect(embedIframe).toBeFalsy();
|
||||
|
@ -11,7 +13,8 @@ test("should open embed iframe on click", async ({ page }) => {
|
|||
await page.click('[data-cal-link="free?light&popup"]');
|
||||
|
||||
embedIframe = await getEmbedIframe({ page, pathname: "/free" });
|
||||
expect(embedIframe).toBeEmbedCalLink({
|
||||
|
||||
expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||
pathname: "/free",
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,10 +3,15 @@ import { expect, Frame } from "@playwright/test";
|
|||
import { test } from "../fixtures/fixtures";
|
||||
import { todo, getEmbedIframe } from "../lib/testUtils";
|
||||
|
||||
test("Inline Iframe - Configured with Dark Theme", async ({ page }) => {
|
||||
test("Inline Iframe - Configured with Dark Theme", async ({
|
||||
page,
|
||||
getActionFiredDetails,
|
||||
addEmbedListeners,
|
||||
}) => {
|
||||
await addEmbedListeners("");
|
||||
await page.goto("/?only=ns:default");
|
||||
const embedIframe = await getEmbedIframe({ page, pathname: "/pro" });
|
||||
expect(embedIframe).toBeEmbedCalLink({
|
||||
expect(embedIframe).toBeEmbedCalLink("", getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
searchParams: {
|
||||
theme: "dark",
|
||||
|
|
|
@ -198,6 +198,12 @@ function getNamespace() {
|
|||
|
||||
const isEmbed = () => {
|
||||
const namespace = getNamespace();
|
||||
const _isValidNamespace = isValidNamespace(namespace);
|
||||
if (parent !== window && !_isValidNamespace) {
|
||||
log(
|
||||
"Looks like you have iframed cal.com but not using Embed Snippet. Directly using an iframe isn't recommended."
|
||||
);
|
||||
}
|
||||
return isValidNamespace(namespace);
|
||||
};
|
||||
|
||||
|
@ -288,7 +294,7 @@ function keepParentInformedAboutDimensionChanges() {
|
|||
return;
|
||||
}
|
||||
if (!embedStore.windowLoadEventFired) {
|
||||
sdkActionManager?.fire("windowLoadComplete", {});
|
||||
sdkActionManager?.fire("__windowLoadComplete", {});
|
||||
}
|
||||
embedStore.windowLoadEventFired = true;
|
||||
|
||||
|
@ -308,7 +314,7 @@ function keepParentInformedAboutDimensionChanges() {
|
|||
knownIframeHeight = iframeHeight;
|
||||
numDimensionChanges++;
|
||||
// FIXME: This event shouldn't be subscribable by the user. Only by the SDK.
|
||||
sdkActionManager?.fire("dimension-changed", {
|
||||
sdkActionManager?.fire("__dimensionChanged", {
|
||||
iframeHeight,
|
||||
iframeWidth,
|
||||
isFirstTime,
|
||||
|
@ -357,7 +363,7 @@ if (isBrowser) {
|
|||
|
||||
if (!pageStatus || pageStatus == "200") {
|
||||
keepParentInformedAboutDimensionChanges();
|
||||
sdkActionManager?.fire("iframeReady", {});
|
||||
sdkActionManager?.fire("__iframeReady", {});
|
||||
} else
|
||||
sdkActionManager?.fire("linkFailed", {
|
||||
code: pageStatus,
|
||||
|
|
|
@ -330,7 +330,7 @@ export class Cal {
|
|||
// 1. Initial iframe width and height would be according to 100% value of the parent element
|
||||
// 2. Once webpage inside iframe renders, it would tell how much iframe height should be increased so that my entire content is visible without iframe scroll
|
||||
// 3. Parent window would check what iframe height can be set according to parent Element
|
||||
this.actionManager.on("dimension-changed", (e) => {
|
||||
this.actionManager.on("__dimensionChanged", (e) => {
|
||||
const { data } = e.detail;
|
||||
const iframe = this.iframe!;
|
||||
|
||||
|
@ -347,7 +347,7 @@ export class Cal {
|
|||
}
|
||||
});
|
||||
|
||||
this.actionManager.on("iframeReady", (e) => {
|
||||
this.actionManager.on("__iframeReady", (e) => {
|
||||
this.iframeReady = true;
|
||||
this.doInIframe({ method: "parentKnowsIframeReady", arg: undefined });
|
||||
this.iframeDoQueue.forEach(({ method, arg }) => {
|
||||
|
|
Loading…
Reference in a new issue