Skip to content

Commit

Permalink
feat: add tags to portals (#3975)
Browse files Browse the repository at this point in the history
  • Loading branch information
RODO94 authored Nov 20, 2024
1 parent 8b7f886 commit 912d1c3
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test("adding an external portal", async () => {
type: TYPES.ExternalPortal,
data: {
flowId: "b",
tags: [],
},
}),
);
Expand Down Expand Up @@ -63,6 +64,7 @@ test("changing an external portal", async () => {
type: TYPES.ExternalPortal,
data: {
flowId: "a",
tags: [],
},
}),
);
Expand Down
14 changes: 12 additions & 2 deletions editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -16,10 +20,12 @@ const ExternalPortalForm: React.FC<{
flowId?: string;
handleSubmit?: (val: any) => void;
flows?: Array<Flow>;
}> = ({ id, handleSubmit, flowId = "", flows = [] }) => {
tags?: NodeTag[];
}> = ({ id, handleSubmit, flowId = "", flows = [], tags = [] }) => {
const formik = useFormik({
initialValues: {
flowId,
tags,
},
onSubmit: (values) => {
if (handleSubmit) {
Expand Down Expand Up @@ -58,6 +64,10 @@ const ExternalPortalForm: React.FC<{
))}
</select>
</ModalSectionContent>
<ComponentTagSelect
value={formik.values.tags}
onChange={(value) => formik.setFieldValue("tags", value)}
/>
</ModalSection>
</form>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -11,33 +11,34 @@ describe("adding an internal portal", () => {
test("creating a new internal portal", async () => {
const handleSubmit = vi.fn();

const { user } = setup(
const { user, getByTestId } = setup(
<InternalPortalForm
flows={[{ id: "ignore", text: "ignore" }]}
handleSubmit={handleSubmit}
/>,
);

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({
type: TYPES.InternalPortal,
data: {
flowId: "", // will be removed when saving the data
text: "new internal portal",
tags: [],
},
});
});
Expand All @@ -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");
});
Expand All @@ -72,17 +78,24 @@ describe("adding an internal portal", () => {

const { user } = setup(
<InternalPortalForm
flows={[{ id: "portal", text: "portal" }]}
flows={[{ id: "portal", text: "portal text" }]}
handleSubmit={handleSubmit}
/>,
);

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");
});
Expand All @@ -103,20 +116,21 @@ 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({
type: TYPES.InternalPortal,
data: {
flowId: "", // will be removed when saving the data
text: "new val",
tags: [],
},
});
});
Expand All @@ -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) {
Expand Down
112 changes: 66 additions & 46 deletions editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,20 +26,22 @@ const InternalPortalForm: React.FC<{
flowId?: string;
handleSubmit?: (val: any) => void;
flows?: Array<Flow>;
}> = ({ id, handleSubmit, text = "", flowId = "", flows = [] }) => {
tags?: NodeTag[];
}> = ({ handleSubmit, text = "", flowId = "", flows = [], tags = [] }) => {
const formik = useFormik({
initialValues: {
text,
flowId,
tags,
},
validate: (values) => {
const errors: Record<string, string> = {};

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) => {
Expand All @@ -46,46 +58,54 @@ const InternalPortalForm: React.FC<{

return (
<form id="modal" onSubmit={formik.handleSubmit} data-testid="form">
<div>
<label htmlFor="portalFlowId">Create new internal portal: </label>
<InputField
name="text"
onChange={formik.handleChange}
placeholder="Portal name"
rows={2}
value={formik.values.text}
disabled={!!formik.values.flowId}
id="portalFlowId"
// required={!formik.values.flowId} (was ignored by @testing-library?)
<ModalSection>
<ModalSectionContent
title="Internal Portal"
Icon={ICONS[TYPES.InternalPortal]}
>
<ErrorWrapper error={formik.errors.text}>
<Input
name="text"
onChange={formik.handleChange}
placeholder="Enter a portal name"
rows={2}
value={formik.values.text}
disabled={!!formik.values.flowId}
id="portalFlowId"
/>
</ErrorWrapper>
</ModalSectionContent>
{flows?.length > 0 && (
<ModalSectionContent subtitle="Use an existing portal">
<InputLabel
label="Use an existing portal"
id="flowId-label"
hidden
htmlFor="flowId"
/>
<SelectInput
aria-describedby="flowId"
labelId="flowId-label"
id="flowId"
data-testid="flowId"
name="flowId"
value={formik.values.flowId}
onChange={formik.handleChange}
disabled={!!formik.values.text}
>
{flows.map((flow) => (
<MenuItem key={flow.id} value={flow.id}>
{flow.text}
</MenuItem>
))}
</SelectInput>
</ModalSectionContent>
)}
<ComponentTagSelect
value={formik.values.tags}
onChange={(value) => formik.setFieldValue("tags", value)}
/>
{formik.errors.text && <FormError message={formik.errors.text} />}
</div>
{flows?.length > 0 && (
<>
<span>
<br /> OR
<br />
<br />
<label htmlFor="flowId">Point to an existing portal:</label>
<br />
</span>
<select
id="flowId"
data-testid="flowId"
name="flowId"
value={formik.values.flowId}
onChange={formik.handleChange}
disabled={!!formik.values.text}
>
{!id && <option value="" />}
{flows.map((flow) => (
<option key={flow.id} value={flow.id}>
{flow.text}
</option>
))}
</select>
</>
)}
</ModalSection>
</form>
);
};
Expand Down
Loading

0 comments on commit 912d1c3

Please sign in to comment.