diff --git a/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.test.tsx b/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.test.tsx index 1d4f113fce..f59445b4ca 100644 --- a/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.test.tsx +++ b/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.test.tsx @@ -31,6 +31,7 @@ test("adding an external portal", async () => { type: TYPES.ExternalPortal, data: { flowId: "b", + tags: [], }, }), ); @@ -63,6 +64,7 @@ test("changing an external portal", async () => { type: TYPES.ExternalPortal, data: { flowId: "a", + tags: [], }, }), ); diff --git a/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx b/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx index 99735c9aaf..07c796c529 100644 --- a/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx @@ -1,6 +1,10 @@ -import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; +import { + ComponentType as TYPES, + NodeTag, +} from "@opensystemslab/planx-core/types"; import { useFormik } from "formik"; import React from "react"; +import { ComponentTagSelect } from "ui/editor/ComponentTagSelect"; import ModalSection from "ui/editor/ModalSection"; import ModalSectionContent from "ui/editor/ModalSectionContent"; @@ -16,10 +20,12 @@ const ExternalPortalForm: React.FC<{ flowId?: string; handleSubmit?: (val: any) => void; flows?: Array; -}> = ({ id, handleSubmit, flowId = "", flows = [] }) => { + tags?: NodeTag[]; +}> = ({ id, handleSubmit, flowId = "", flows = [], tags = [] }) => { const formik = useFormik({ initialValues: { flowId, + tags, }, onSubmit: (values) => { if (handleSubmit) { @@ -58,6 +64,10 @@ const ExternalPortalForm: React.FC<{ ))} + formik.setFieldValue("tags", value)} + /> ); diff --git a/editor.planx.uk/src/@planx/components/InternalPortal/Editor.test.tsx b/editor.planx.uk/src/@planx/components/InternalPortal/Editor.test.tsx index d1d002196d..f95f97f9a9 100644 --- a/editor.planx.uk/src/@planx/components/InternalPortal/Editor.test.tsx +++ b/editor.planx.uk/src/@planx/components/InternalPortal/Editor.test.tsx @@ -1,5 +1,5 @@ import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; -import { fireEvent, screen, waitFor } from "@testing-library/react"; +import { fireEvent, screen, waitFor, within } from "@testing-library/react"; import React from "react"; import { setup } from "testUtils"; import { vi } from "vitest"; @@ -11,26 +11,26 @@ describe("adding an internal portal", () => { test("creating a new internal portal", async () => { const handleSubmit = vi.fn(); - const { user } = setup( + const { user, getByTestId } = setup( , ); - const flowSelect = screen.getByTestId("flowId"); + const existingFlowInput = getByTestId("flowId"); + const flowSelect = within(existingFlowInput).getByRole("combobox"); - expect(flowSelect).toHaveValue(""); expect(flowSelect).toBeEnabled(); await user.type( - screen.getByPlaceholderText("Portal name"), + screen.getByPlaceholderText("Enter a portal name"), "new internal portal", ); - expect(flowSelect).toBeDisabled(); + expect(flowSelect).toHaveAttribute("aria-disabled", "true"); - await fireEvent.submit(screen.getByTestId("form")); + fireEvent.submit(screen.getByTestId("form")); await waitFor(() => { expect(handleSubmit).toHaveBeenCalledWith({ @@ -38,6 +38,7 @@ describe("adding an internal portal", () => { data: { flowId: "", // will be removed when saving the data text: "new internal portal", + tags: [], }, }); }); @@ -53,15 +54,20 @@ describe("adding an internal portal", () => { />, ); - const dropdown = screen.queryByTestId("flowId"); + const dropdown = await screen.findByRole("combobox", { + name: "Use an existing portal", + }); + expect(dropdown).toBeInTheDocument(); + + await user.click(dropdown); - expect(dropdown).toHaveValue(""); + const option = await screen.findByRole("option", { name: "portal" }); + expect(option).toBeInTheDocument(); - if (dropdown) { - await user.selectOptions(dropdown, "portal"); - } + await user.click(option); + + fireEvent.submit(screen.getByTestId("form")); - await fireEvent.submit(screen.getByTestId("form")); await waitFor(() => { expect(handleSubmit).toHaveBeenCalledWith("portal"); }); @@ -72,17 +78,24 @@ describe("adding an internal portal", () => { const { user } = setup( , ); - const dropdown = screen.queryByTestId("flowId"); - if (dropdown) { - await user.selectOptions(dropdown, "portal"); - } + const dropdown = await screen.findByRole("combobox", { + name: "Use an existing portal", + }); + expect(dropdown).toBeInTheDocument(); + + await user.click(dropdown); + + const option = await screen.findByRole("option", { name: "portal text" }); + expect(option).toBeInTheDocument(); + + await user.click(option); + fireEvent.submit(screen.getByTestId("form")); - await fireEvent.submit(screen.getByTestId("form")); await waitFor(() => { expect(handleSubmit).toHaveBeenCalledWith("portal"); }); @@ -103,13 +116,13 @@ test("updating an internal portal", async () => { expect(screen.queryByTestId("flowId")).not.toBeInTheDocument(); - const textInput = screen.getByPlaceholderText("Portal name"); + const textInput = screen.getByPlaceholderText("Enter a portal name"); expect(textInput).toHaveValue("val"); await user.clear(textInput); await user.type(textInput, "new val"); - await fireEvent.submit(screen.getByTestId("form")); + fireEvent.submit(screen.getByTestId("form")); await waitFor(() => { expect(handleSubmit).toHaveBeenCalledWith({ @@ -117,6 +130,7 @@ test("updating an internal portal", async () => { data: { flowId: "", // will be removed when saving the data text: "new val", + tags: [], }, }); }); @@ -125,18 +139,22 @@ test("updating an internal portal", async () => { describe("validations", () => { describe("if no flowId is chosen", () => { const scenarios = [ - { action: "adding without flows", error: "Required." }, - { action: "updating without flows", id: "test", error: "Required." }, + { action: "adding without flows", error: "Enter a portal name" }, + { + action: "updating without flows", + id: "test", + error: "Enter a portal name", + }, { action: "adding with flows", flows: [{ id: "portal", text: "portal" }], - error: "Required if no flow is selected", + error: "Enter a portal name or select an existing portal", }, { action: "updating with flows", flows: [{ id: "portal", text: "portal" }], id: "test", - error: "Required if no flow is selected", + error: "Enter a portal name or select an existing portal", }, ]; for (const scenario of scenarios) { diff --git a/editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx b/editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx index 01a3af5944..8a1aa4b7e7 100644 --- a/editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx @@ -1,9 +1,19 @@ -import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; +import MenuItem from "@mui/material/MenuItem"; +import { + ComponentType as TYPES, + NodeTag, +} from "@opensystemslab/planx-core/types"; import { useFormik } from "formik"; import React from "react"; -import InputField from "ui/editor/InputField/InputField"; +import { ComponentTagSelect } from "ui/editor/ComponentTagSelect"; +import ModalSection from "ui/editor/ModalSection"; +import ModalSectionContent from "ui/editor/ModalSectionContent"; +import SelectInput from "ui/editor/SelectInput/SelectInput"; +import InputLabel from "ui/public/InputLabel"; +import ErrorWrapper from "ui/shared/ErrorWrapper"; +import Input from "ui/shared/Input/Input"; -import { FormError } from "../shared/types"; +import { ICONS } from "../shared/icons"; interface Flow { id: string; @@ -16,20 +26,22 @@ const InternalPortalForm: React.FC<{ flowId?: string; handleSubmit?: (val: any) => void; flows?: Array; -}> = ({ id, handleSubmit, text = "", flowId = "", flows = [] }) => { + tags?: NodeTag[]; +}> = ({ handleSubmit, text = "", flowId = "", flows = [], tags = [] }) => { const formik = useFormik({ initialValues: { text, flowId, + tags, }, validate: (values) => { const errors: Record = {}; - if (!values.flowId && !values.text) { errors.text = - flows.length > 0 ? "Required if no flow is selected" : "Required."; + flows.length > 0 + ? "Enter a portal name or select an existing portal" + : "Enter a portal name"; } - return errors; }, onSubmit: (values) => { @@ -46,46 +58,54 @@ const InternalPortalForm: React.FC<{ return ( ); }; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Portal.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Portal.tsx index b750a47885..aee50252dd 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Portal.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Portal.tsx @@ -1,6 +1,7 @@ import { useQuery } from "@apollo/client"; import MoreVert from "@mui/icons-material/MoreVert"; -import { ComponentType } from "@opensystemslab/planx-core/types"; +import Box from "@mui/material/Box"; +import { ComponentType, NodeTag } from "@opensystemslab/planx-core/types"; import { ICONS } from "@planx/components/shared/icons"; import classNames from "classnames"; import gql from "graphql-tag"; @@ -14,6 +15,7 @@ import { rootFlowPath } from "../../../../../routes/utils"; import { getParentId } from "../lib/utils"; import Hanger from "./Hanger"; import Question from "./Question"; +import { Tag } from "./Tag"; const ExternalPortal: React.FC = (props) => { const [href, setHref] = useState("Loading..."); @@ -87,14 +89,17 @@ const ExternalPortal: React.FC = (props) => { return ( <>