diff --git a/src/components/WithHelpText.tsx b/src/components/WithHelpText.tsx index a99dd6e64..b4cb170f0 100644 --- a/src/components/WithHelpText.tsx +++ b/src/components/WithHelpText.tsx @@ -78,7 +78,7 @@ const Root = styled('div')(( }, [`& .${classes.notchedChild}`]: { - padding: theme.spacing(1), + padding: theme.spacing(1, 1.75), }, [`& .${classes.verticalHr}`]: { display: "flex", diff --git a/src/components/ga4/EventBuilder/ValidateEvent/index.spec.tsx b/src/components/ga4/EventBuilder/ValidateEvent/index.spec.tsx new file mode 100644 index 000000000..0ed90e1a2 --- /dev/null +++ b/src/components/ga4/EventBuilder/ValidateEvent/index.spec.tsx @@ -0,0 +1,95 @@ +import React from "react" +import { render, screen, fireEvent, within } from "@testing-library/react" +import "@testing-library/jest-dom" +import ValidateEvent, { ValidateEventProps } from "." +import { EventCtx, EventPayload } from ".." +import useValidateEvent from "./useValidateEvent" +import { EventType } from "../types" + +// Mock the useValidateEvent hook. This allows us to control its output and check if it's called correctly. +jest.mock("./useValidateEvent", () => ({ + __esModule: true, + default: jest.fn(), +})) + +const mockedUseValidateEvent = useValidateEvent as jest.Mock + +// Mock child components that are not relevant to this test. +jest.mock("@/components/PrettyJson", () => () =>
PrettyJson
) +jest.mock("@/components/Spinner", () => () =>
Spinner
) + +const mockValidateEventFn = jest.fn() + +// A minimal set of props to render the component. +const defaultProps: ValidateEventProps = { + measurement_id: "", + app_instance_id: "", + firebase_app_id: "", + api_secret: "", + client_id: "", + user_id: "", + formatPayload: jest.fn(), + payloadErrors: undefined, + useTextBox: false, +} + +// A helper to render the component with context. +const renderComponent = (props: Partial = {}) => { + // The component relies on EventCtx for some data. This should be a valid + // EventPayload. + const contextValue: EventPayload = { + instanceId: { + measurement_id: "G-12345", + firebase_app_id: "app:12345", + }, + eventName: "test_event", + type: EventType.CustomEvent, + parameters: [], + items: [], + userProperties: [], + timestamp_micros: "", + non_personalized_ads: false, + useTextBox: false, + payloadObj: [], + api_secret: "secret123", + clientIds: {}, + } + + return render( + + + + ) +} + +describe("ValidateEvent EU endpoint functionality", () => { + beforeEach(() => { + // Reset mocks before each test + jest.clearAllMocks() + // Setup the default mock implementation for useValidateEvent to render the initial state. + mockedUseValidateEvent.mockReturnValue({ + status: "not-started", + validateEvent: mockValidateEventFn, + }) + }) + + it("should render with the default endpoint and allow switching to the EU endpoint", () => { + renderComponent() + + // 1. Check initial state (default endpoint) + expect(screen.getByText("HOST: www.google-analytics.com", { exact: false })).toBeInTheDocument() + expect(screen.queryByText("HOST: region1.google-analytics.com", { exact: false })).not.toBeInTheDocument() + expect(mockedUseValidateEvent).toHaveBeenCalledWith(false) + + // 2. Find and interact with the switch + const euSwitch = within(screen.getByTestId("use-eu-endpoint")).getByRole('checkbox') + expect(euSwitch).toHaveProperty('checked', false) + fireEvent.click(euSwitch) + + // 3. Check the new state (EU endpoint) + expect(euSwitch).toHaveProperty('checked', true) + expect(screen.getByText("HOST: region1.google-analytics.com", { exact: false })).toBeInTheDocument() + expect(mockedUseValidateEvent).toHaveBeenCalledTimes(2) + expect(mockedUseValidateEvent).toHaveBeenLastCalledWith(true) + }) +}) diff --git a/src/components/ga4/EventBuilder/ValidateEvent/index.tsx b/src/components/ga4/EventBuilder/ValidateEvent/index.tsx index 43fa96e48..4bff53753 100644 --- a/src/components/ga4/EventBuilder/ValidateEvent/index.tsx +++ b/src/components/ga4/EventBuilder/ValidateEvent/index.tsx @@ -21,6 +21,8 @@ import clsx from "classnames" import useValidateEvent from "./useValidateEvent" import Loadable from "@/components/Loadable" import Typography from "@mui/material/Typography" +import Grid from "@mui/material/Grid" +import Switch from "@mui/material/Switch" import { PAB, PlainButton } from "@/components/Buttons" import { Check, Warning, Error as ErrorIcon } from "@mui/icons-material" import PrettyJson from "@/components/PrettyJson" @@ -28,8 +30,9 @@ import usePayload from "./usePayload" import { ValidationMessage } from "../types" import Spinner from "@/components/Spinner" import { EventCtx, Label } from ".." -import { Card } from "@mui/material" +import { Box, Card } from "@mui/material" import { green, red } from "@mui/material/colors" +import WithHelpText from "@/components/WithHelpText" const PREFIX = 'ValidateEvent'; @@ -47,6 +50,7 @@ interface TemplateProps { sent?: boolean payloadErrors?: string | undefined useTextBox?: boolean + useEuEndpoint: boolean } export interface ValidateEventProps { @@ -166,16 +170,14 @@ const Template: React.FC = ({ error, valid, payloadErrors, - useTextBox + useTextBox, + useEuEndpoint, }) => { const { instanceId, api_secret } = useContext(EventCtx)! const payload = usePayload() return ( - + <> {headingIcon} {heading} @@ -249,9 +251,9 @@ const Template: React.FC = ({ {instanceId.firebase_app_id && `&firebase_app_id=${instanceId.firebase_app_id}`} {instanceId.measurement_id && - `&measurement_id=${instanceId.measurement_id}`}{" "} + `&measurement_id=${instanceId.measurement_id}`}{" "}
HTTP/1.1
- HOST: www.google-analytics.com
+ HOST: {useEuEndpoint ? "region1.google-analytics.com" : "www.google-analytics.com"}
Content-Type: application/json
@@ -265,87 +267,124 @@ const Template: React.FC = ({ tooltipText="Copy payload" /> -
+ ) } const ValidateEvent: React.FC = ({formatPayload, payloadErrors, useTextBox}) => { - const request = useValidateEvent() + const [useEuEndpoint, setUseEuEndpoint] = React.useState(false) + const request = useValidateEvent(useEuEndpoint) return ( - ( -