test: verify embed helpers (#11318)

* test: add test cases to `window.getEmbedNamespace`

* test: add test cases when window.name doesn't have cal-embed=

* test: add test case to `getembedTheme` returning the theme from embedStore

* test: add test to `getEmbedTheme` returning the theme from query params

* Small refactor to reduce the time taken by tests and fix missing typings for embed functions on window

* Fix weird test failure

* Fix type import error caught by api.test.test

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
Wesley
2023-09-14 07:14:54 +00:00
committed by GitHub
co-authored by Hariom Balhara
parent 2591ad12df
commit dfde17111c
6 changed files with 130 additions and 66 deletions
@@ -1,4 +1,4 @@
import type { EmbedThemeConfig } from "./embed-iframe";
import type { EmbedThemeConfig } from "./src/types";
export default function EmbedInitIframe() {
if (typeof window === "undefined" || window.isEmbed) {
@@ -2,6 +2,8 @@ import { expect } from "@playwright/test";
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");
@@ -45,18 +47,77 @@ test.describe("Embed Pages", () => {
expect(marginFromTop).not.toBe(0);
});
test("should change to embed when window.name is changed to cal-embed=", async ({ page }) => {
await page.goto("http://localhost:3000/free/30min");
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.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);
});
await page.reload();
const isEmbed = await page.evaluate(() => {
return window?.isEmbed?.();
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);
});
expect(isEmbed).toBe(true);
});
});
+10 -48
View File
@@ -1,29 +1,22 @@
import { useSearchParams } from "next/navigation";
import type { CSSProperties } from "react";
import { useEffect, useRef, useState, useCallback } from "react";
import type { Message } from "./embed";
import { sdkActionManager } from "./sdk-event";
type Theme = "dark" | "light";
export type BookerLayouts = "month_view" | "week_view" | "column_view";
export type EmbedThemeConfig = Theme | "auto";
export type UiConfig = {
hideEventTypeDetails?: boolean;
// If theme not provided we would get null
theme?: EmbedThemeConfig | null;
styles?: EmbedStyles & EmbedNonStylesConfig;
//TODO: Extract from tailwind the list of all custom variables and support them in auto-completion as well as runtime validation. Followup with listing all variables in Embed Snippet Generator UI.
cssVarsPerTheme?: Record<Theme, Record<string, string>>;
layout?: BookerLayouts;
colorScheme?: string | null;
};
import type { EmbedThemeConfig, UiConfig, EmbedNonStylesConfig, BookerLayouts, EmbedStyles } from "./types";
type SetStyles = React.Dispatch<React.SetStateAction<EmbedStyles>>;
type setNonStylesConfig = React.Dispatch<React.SetStateAction<EmbedNonStylesConfig>>;
declare global {
interface Window {
CalEmbed: {
__logQueue?: unknown[];
embedStore: typeof embedStore;
applyCssVars: (cssVarsPerTheme: UiConfig["cssVarsPerTheme"]) => void;
};
}
}
/**
* This is in-memory persistence needed so that when user browses through the embed, the configurations from the instructions aren't lost.
*/
@@ -47,20 +40,6 @@ const embedStore = {
setUiConfig: [] as ((arg0: UiConfig) => void)[],
};
declare global {
interface Window {
CalEmbed: {
__logQueue?: unknown[];
embedStore: typeof embedStore;
applyCssVars: (cssVarsPerTheme: UiConfig["cssVarsPerTheme"]) => void;
};
CalComPageStatus: string;
isEmbed?: () => boolean;
getEmbedNamespace: () => string | null;
getEmbedTheme: () => EmbedThemeConfig | null;
}
}
let isSafariBrowser = false;
const isBrowser = typeof window !== "undefined";
@@ -100,23 +79,6 @@ function log(...args: unknown[]) {
}
}
// Only allow certain styles to be modified so that when we make any changes to HTML, we know what all embed styles might be impacted.
// Keep this list to minimum, only adding those styles which are really needed.
interface EmbedStyles {
body?: Pick<CSSProperties, "background">;
eventTypeListItem?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
enabledDateButton?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
disabledDateButton?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
availabilityDatePicker?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
}
interface EmbedNonStylesConfig {
/** Default would be center */
align?: "left";
branding?: {
brandColor?: string;
};
}
const setEmbedStyles = (stylesConfig: EmbedStyles) => {
embedStore.styles = stylesConfig;
for (const [, setEmbedStyle] of Object.entries(embedStore.reactStylesStateSetters)) {
+2 -7
View File
@@ -2,17 +2,12 @@
import { FloatingButton } from "./FloatingButton/FloatingButton";
import { Inline } from "./Inline/inline";
import { ModalBox } from "./ModalBox/ModalBox";
import type {
InterfaceWithParent,
interfaceWithParent,
UiConfig,
EmbedThemeConfig,
BookerLayouts,
} from "./embed-iframe";
import type { InterfaceWithParent, interfaceWithParent } from "./embed-iframe";
import css from "./embed.css";
import { SdkActionManager } from "./sdk-action-manager";
import type { EventData, EventDataMap } from "./sdk-action-manager";
import allCss from "./tailwind.generated.css?inline";
import type { UiConfig, EmbedThemeConfig, BookerLayouts } from "./types";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Rest<T extends any[]> = T extends [any, ...infer U] ? U : never;
+45
View File
@@ -0,0 +1,45 @@
import type { CSSProperties } from "react";
type Theme = "dark" | "light";
export type EmbedThemeConfig = Theme | "auto";
export type BookerLayouts = "month_view" | "week_view" | "column_view";
// Only allow certain styles to be modified so that when we make any changes to HTML, we know what all embed styles might be impacted.
// Keep this list to minimum, only adding those styles which are really needed.
export interface EmbedStyles {
body?: Pick<CSSProperties, "background">;
eventTypeListItem?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
enabledDateButton?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
disabledDateButton?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
availabilityDatePicker?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
}
export interface EmbedNonStylesConfig {
/** Default would be center */
align?: "left";
branding?: {
brandColor?: string;
};
}
export type UiConfig = {
hideEventTypeDetails?: boolean;
// If theme not provided we would get null
theme?: EmbedThemeConfig | null;
styles?: EmbedStyles & EmbedNonStylesConfig;
//TODO: Extract from tailwind the list of all custom variables and support them in auto-completion as well as runtime validation. Followup with listing all variables in Embed Snippet Generator UI.
cssVarsPerTheme?: Record<Theme, Record<string, string>>;
layout?: BookerLayouts;
colorScheme?: string | null;
};
declare global {
interface Window {
CalComPageStatus: string;
isEmbed?: () => boolean;
getEmbedNamespace: () => string | null;
getEmbedTheme: () => EmbedThemeConfig | null;
}
}
export {};
@@ -14,9 +14,10 @@ const api = getCalApi();
test("Check that the API is available", async () => {
expect(api).toBeDefined()
const awaitedApi = await api;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
awaitedApi('floatingButton', {
calLink: 'free',
config: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error We are intentionaly testing invalid value
layout: 'wrongview'
}