From dfde17111c826363455d8402128a882b5f82faae Mon Sep 17 00:00:00 2001 From: Wesley <100464352+ologbonowiwi@users.noreply.github.com> Date: Thu, 14 Sep 2023 04:14:54 -0300 Subject: [PATCH] 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 --- .../embeds/embed-core/embed-iframe-init.ts | 2 +- .../playwright/tests/embed-pages.e2e.ts | 79 ++++++++++++++++--- .../embeds/embed-core/src/embed-iframe.ts | 58 +++----------- packages/embeds/embed-core/src/embed.ts | 9 +-- packages/embeds/embed-core/src/types.ts | 45 +++++++++++ .../embed-react/test/packaged/api.test.ts | 3 +- 6 files changed, 130 insertions(+), 66 deletions(-) create mode 100644 packages/embeds/embed-core/src/types.ts diff --git a/packages/embeds/embed-core/embed-iframe-init.ts b/packages/embeds/embed-core/embed-iframe-init.ts index e669599c61..c49f29a0fd 100644 --- a/packages/embeds/embed-core/embed-iframe-init.ts +++ b/packages/embeds/embed-core/embed-iframe-init.ts @@ -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) { diff --git a/packages/embeds/embed-core/playwright/tests/embed-pages.e2e.ts b/packages/embeds/embed-core/playwright/tests/embed-pages.e2e.ts index d7de332a52..3133d350db 100644 --- a/packages/embeds/embed-core/playwright/tests/embed-pages.e2e.ts +++ b/packages/embeds/embed-core/playwright/tests/embed-pages.e2e.ts @@ -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); }); }); diff --git a/packages/embeds/embed-core/src/embed-iframe.ts b/packages/embeds/embed-core/src/embed-iframe.ts index 856c59fca9..7671baf5b1 100644 --- a/packages/embeds/embed-core/src/embed-iframe.ts +++ b/packages/embeds/embed-core/src/embed-iframe.ts @@ -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>; - layout?: BookerLayouts; - colorScheme?: string | null; -}; +import type { EmbedThemeConfig, UiConfig, EmbedNonStylesConfig, BookerLayouts, EmbedStyles } from "./types"; type SetStyles = React.Dispatch>; type setNonStylesConfig = React.Dispatch>; +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; - eventTypeListItem?: Pick; - enabledDateButton?: Pick; - disabledDateButton?: Pick; - availabilityDatePicker?: Pick; -} -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)) { diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts index a464151f1b..a8db717899 100644 --- a/packages/embeds/embed-core/src/embed.ts +++ b/packages/embeds/embed-core/src/embed.ts @@ -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, ...infer U] ? U : never; diff --git a/packages/embeds/embed-core/src/types.ts b/packages/embeds/embed-core/src/types.ts new file mode 100644 index 0000000000..6e56223d49 --- /dev/null +++ b/packages/embeds/embed-core/src/types.ts @@ -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; + eventTypeListItem?: Pick; + enabledDateButton?: Pick; + disabledDateButton?: Pick; + availabilityDatePicker?: Pick; +} + +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>; + layout?: BookerLayouts; + colorScheme?: string | null; +}; + +declare global { + interface Window { + CalComPageStatus: string; + isEmbed?: () => boolean; + getEmbedNamespace: () => string | null; + getEmbedTheme: () => EmbedThemeConfig | null; + } +} + +export {}; diff --git a/packages/embeds/embed-react/test/packaged/api.test.ts b/packages/embeds/embed-react/test/packaged/api.test.ts index ec4aebf891..0334f9dc88 100644 --- a/packages/embeds/embed-react/test/packaged/api.test.ts +++ b/packages/embeds/embed-react/test/packaged/api.test.ts @@ -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' }