From 2b2b3b8e4217287cf4f41ecf5ae91f52c38dc4f4 Mon Sep 17 00:00:00 2001 From: John Davey Date: Thu, 1 Jun 2023 12:41:34 +0100 Subject: [PATCH] NXT-149 Reinstate jotforms files from broken PR to resolve teamcity VSC issues after Ians account deactivated --- .../JotFormEmbed/JotFormEmbed.module.scss | 8 + .../JotFormEmbed/JotFormEmbed.test.tsx | 177 +++++++++++++++++ .../components/JotFormEmbed/JotFormEmbed.tsx | 188 ++++++++++++++++++ .../JotFormPage/JotFormPage.test.tsx | 74 +++++++ .../components/JotFormPage/JotFormPage.tsx | 48 +++++ .../JotFormPage/getGetServerSideProps.test.ts | 131 ++++++++++++ .../JotFormPage/getGetServerSideProps.ts | 48 +++++ web/src/feeds/jotform/jotform.ts | 22 ++ web/src/feeds/jotform/types.ts | 51 +++++ ...procedures-notification.page.test.tsx.snap | 186 +++++++++++++++++ ...-nice-content-in-the-uk.page.test.tsx.snap | 96 +++++++++ ...onal-procedures-notification.page.test.tsx | 46 +++++ ...ventional-procedures-notification.page.tsx | 45 +++++ ...se-of-nice-content-in-the-uk.page.test.tsx | 46 +++++ .../use-of-nice-content-in-the-uk.page.tsx | 25 +++ 15 files changed, 1191 insertions(+) create mode 100644 web/src/components/JotFormEmbed/JotFormEmbed.module.scss create mode 100644 web/src/components/JotFormEmbed/JotFormEmbed.test.tsx create mode 100644 web/src/components/JotFormEmbed/JotFormEmbed.tsx create mode 100644 web/src/components/JotFormPage/JotFormPage.test.tsx create mode 100644 web/src/components/JotFormPage/JotFormPage.tsx create mode 100644 web/src/components/JotFormPage/getGetServerSideProps.test.ts create mode 100644 web/src/components/JotFormPage/getGetServerSideProps.ts create mode 100644 web/src/feeds/jotform/jotform.ts create mode 100644 web/src/feeds/jotform/types.ts create mode 100644 web/src/pages/forms/__snapshots__/interventional-procedures-notification.page.test.tsx.snap create mode 100644 web/src/pages/forms/__snapshots__/use-of-nice-content-in-the-uk.page.test.tsx.snap create mode 100644 web/src/pages/forms/interventional-procedures-notification.page.test.tsx create mode 100644 web/src/pages/forms/interventional-procedures-notification.page.tsx create mode 100644 web/src/pages/forms/use-of-nice-content-in-the-uk.page.test.tsx create mode 100644 web/src/pages/forms/use-of-nice-content-in-the-uk.page.tsx diff --git a/web/src/components/JotFormEmbed/JotFormEmbed.module.scss b/web/src/components/JotFormEmbed/JotFormEmbed.module.scss new file mode 100644 index 000000000..52ff438a0 --- /dev/null +++ b/web/src/components/JotFormEmbed/JotFormEmbed.module.scss @@ -0,0 +1,8 @@ +.iframe { + background: transparent; + border: 0; + max-width: 100%; + min-height: 540px; + min-width: 100%; + width: 100%; +} diff --git a/web/src/components/JotFormEmbed/JotFormEmbed.test.tsx b/web/src/components/JotFormEmbed/JotFormEmbed.test.tsx new file mode 100644 index 000000000..7cc0f2f24 --- /dev/null +++ b/web/src/components/JotFormEmbed/JotFormEmbed.test.tsx @@ -0,0 +1,177 @@ +import { render, screen, fireEvent } from "@testing-library/react"; + +import { JotFormEmbed } from "./JotFormEmbed"; + +describe("JotFormEmbed", () => { + it("should render iframe with title", () => { + render(); + + expect(screen.getByTitle("This is a title")).toHaveProperty( + "tagName", + "IFRAME" + ); + }); + + it("should create iframe source URL from form id, JotForm base URL and isIframeEmbed querystring", () => { + render(); + + expect(screen.getByTitle("This is a title")).toHaveAttribute( + "src", + "https://next-web-tests.jotform.com/1234?isIframeEmbed=1" + ); + }); + + it("should allow full screen on iframe", () => { + render(); + + expect(screen.getByTitle("This is a title")).toHaveAttribute( + "allowfullscreen", + "" + ); + }); + + it("should allow geolocation, microphone and camera on iframe", () => { + render(); + + expect(screen.getByTitle("This is a title")).toHaveAttribute( + "allow", + "geolocation; microphone; camera" + ); + }); + + it("should use hidden overflow style", () => { + render(); + + expect(screen.getByTitle("This is a title")).toHaveStyle({ + overflow: "hidden", + }); + }); + + it("should add data attribute with form ID for GTM tracking", () => { + render(); + + expect(screen.getByTitle("This is a title")).toHaveAttribute( + "data-jotform-id", + "1234" + ); + }); + + it("should use given initial height", () => { + render( + + ); + + expect(screen.getByTitle("This is a title")).toHaveStyle({ + height: "999px", + }); + }); + + it("should set height from iframe post message", () => { + render(); + + fireEvent( + window, + new MessageEvent("message", { + data: "setHeight:987:1234", + origin: "https://next-web-tests.jotform.com", + }) + ); + + expect(screen.getByTitle("This is a title")).toHaveStyle({ + height: "987px", + }); + }); + + it("should call given onSubmit callback prop after form submission event", () => { + const onSubmit = jest.fn(); + + render( + + ); + + fireEvent( + window, + new MessageEvent("message", { + data: { + action: "submission-completed", + formID: "1234", + }, + origin: "https://next-web-tests.jotform.com", + }) + ); + + expect(onSubmit).toHaveBeenCalled(); + }); + + it("should push submit event to data layer after form submission message", () => { + const dataLayerPush = jest.spyOn(window.dataLayer, "push"); + + render(); + + fireEvent( + window, + new MessageEvent("message", { + data: { + action: "submission-completed", + formID: "1234", + }, + origin: "https://next-web-tests.jotform.com", + }) + ); + + expect(dataLayerPush).toHaveBeenCalledWith({ + event: "Jotform Message", + jf_id: "1234", + jf_title: "This is a title", + jf_type: "submit", + }); + + dataLayerPush.mockReset(); + }); + + it("should push progress event to data layer after scroll into view message", () => { + const dataLayerPush = jest.spyOn(window.dataLayer, "push"); + + render(); + + fireEvent( + window, + new MessageEvent("message", { + data: "scrollIntoView::1234", + origin: "https://next-web-tests.jotform.com", + }) + ); + + expect(dataLayerPush).toHaveBeenCalledWith({ + event: "Jotform Message", + jf_id: "1234", + jf_title: "This is a title", + jf_type: "progress", + }); + + dataLayerPush.mockReset(); + }); + + it("should scroll iframe into view in response to scrollIntoView message", () => { + render(); + + const iframe = screen.getByTitle("This is a title"), + scrollIntoView = jest.fn(); + + iframe.scrollIntoView = scrollIntoView; + + fireEvent( + window, + new MessageEvent("message", { + data: "scrollIntoView::1234", + origin: "https://next-web-tests.jotform.com", + }) + ); + + expect(scrollIntoView).toHaveBeenCalled(); + }); +}); diff --git a/web/src/components/JotFormEmbed/JotFormEmbed.tsx b/web/src/components/JotFormEmbed/JotFormEmbed.tsx new file mode 100644 index 000000000..6595de608 --- /dev/null +++ b/web/src/components/JotFormEmbed/JotFormEmbed.tsx @@ -0,0 +1,188 @@ +import React, { + CSSProperties, + FC, + useCallback, + useEffect, + useRef, + useState, +} from "react"; + +import { publicRuntimeConfig } from "@/config"; +import type { FormID } from "@/feeds/jotform/jotform"; +import { logger } from "@/logger"; + +import styles from "./JotFormEmbed.module.scss"; + +const jotFormBaseURL = publicRuntimeConfig.jotForm.baseURL; + +interface JotFormEmbedProps { + jotFormID: FormID; + title: string; + /** An optional, initial height */ + height?: number; + onSubmit?: () => void; +} + +type JFMessageObject = { + action: "submission-completed"; + formID: FormID; +}; + +type JFMessageName = + | "scrollIntoView" + | "setHeight" + | "setMinHeight" + | "collapseErrorPage" + | "reloadPage" + | "loadScript" + | "exitFullscreen"; + +type JFMessageString = `${JFMessageName}:${number | ""}:${FormID}`; + +type JFMessageEvent = MessageEvent; + +export const JotFormEmbed: FC = ({ + jotFormID, + title, + height, + onSubmit, +}) => { + const iframeRef = useRef(null), + [styleOverrides, setStyleOverrides] = useState({ + height: height ? `${height}px` : undefined, + }), + handleIFrameMessage = useCallback( + (content?: JFMessageEvent) => { + if (!iframeRef.current || !content || content.origin != jotFormBaseURL) + return; + + const { data } = content; + + // The form completion message is an object rather than a string like other messages so handle it first + if ( + typeof data === "object" && + data.action === "submission-completed" + ) { + window.dataLayer.push({ + event: "Jotform Message", + jf_type: "submit", + jf_id: jotFormID, + jf_title: title, + }); + if (onSubmit) onSubmit(); + return; + } + + // Ignore non-string messages as they should all be strings in the format like "setHeight:1577:230793530776059" + if (typeof data !== "string") return; + + const messageParts = data.split(":"), + [messageName, value, targetFormID] = messageParts, + iframe = iframeRef.current; + + if (targetFormID !== jotFormID) { + logger.warn( + `Form with ID ${jotFormID} didn't match event with form ID ${targetFormID}` + ); + return; + } + + switch (messageName as JFMessageName) { + case "scrollIntoView": + if (typeof iframe.scrollIntoView === "function") + iframe.scrollIntoView(); + // There's no 'page event' sent from JotForm for multi page forms, + // but scrollIntoView is fired for pages so we use this as the closest thing to track pagination + window.dataLayer.push({ + event: "Jotform Message", + jf_type: "progress", + jf_id: jotFormID, + jf_title: title, + }); + break; + case "setHeight": { + const height = parseInt(value, 10) + "px"; + setStyleOverrides((s) => ({ ...s, height })); + break; + } + case "setMinHeight": { + const minHeight = parseInt(value, 10) + "px"; + setStyleOverrides((s) => ({ ...s, minHeight })); + break; + } + case "reloadPage": + if (iframe.contentWindow) { + try { + iframe.contentWindow.location.reload(); + } catch (e) { + window.location.reload(); + } + } else window.location.reload(); + break; + case "collapseErrorPage": + if (iframe.clientHeight > window.innerHeight) { + iframe.style.height = window.innerHeight + "px"; + } + break; + case "exitFullscreen": + if (window.document.exitFullscreen) + window.document.exitFullscreen(); + break; + case "loadScript": { + let src = value; + if (messageParts.length > 3) { + src = value + ":" + messageParts[2]; + } + + const script = document.createElement("script"); + script.src = src; + script.type = "text/javascript"; + document.body.appendChild(script); + break; + } + default: + break; + } + + if (iframe.contentWindow && iframe.contentWindow.postMessage) { + const urls = { + docurl: encodeURIComponent(global.document.URL), + referrer: encodeURIComponent(global.document.referrer), + }; + iframe.contentWindow.postMessage( + JSON.stringify({ type: "urls", value: urls }), + "*" + ); + } + }, + [jotFormID, onSubmit, iframeRef, title] + ); + + useEffect(() => { + window.addEventListener("message", handleIFrameMessage, false); + + return () => + window.removeEventListener("message", handleIFrameMessage, false); + }, [handleIFrameMessage]); + + useEffect(() => { + // Only hide the iframe scroll bar once JS has kicked in and we know we can respond to the setHeight message + setStyleOverrides((s) => ({ ...s, overflow: "hidden" })); + }, []); + + return ( +