Skip to content

Commit

Permalink
feat: display external portals on readme page + test coverage (#4177)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamdelion authored Jan 29, 2025
1 parent f657f91 commit f239666
Show file tree
Hide file tree
Showing 14 changed files with 346 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Meta, StoryObj } from "@storybook/react";

import { ReadMePage } from "./ReadMePage";

const meta = {
title: "Design System/Pages/ReadMe",
component: ReadMePage,
} satisfies Meta<typeof ReadMePage>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Basic = {
args: {
teamSlug: "barnet",
flowSlug: "Apply for prior permission",
flowInformation: {
status: "online",
description: "A long description of a service",
summary: "A short blurb",
limitations: "",
settings: {},
},
},
} satisfies Story;
66 changes: 44 additions & 22 deletions editor.planx.uk/src/pages/FlowEditor/ReadMePage/ReadMePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import Typography from "@mui/material/Typography";
import { TextInputType } from "@planx/components/TextInput/model";
import { useFormik } from "formik";
import { useToast } from "hooks/useToast";
import React from "react";
import capitalize from "lodash/capitalize";
import React, { useState } from "react";
import FlowTag from "ui/editor/FlowTag/FlowTag";
import { FlowTagType, StatusVariant } from "ui/editor/FlowTag/types";
import InputGroup from "ui/editor/InputGroup";
Expand All @@ -16,25 +17,17 @@ import SettingsSection from "ui/editor/SettingsSection";
import { CharacterCounter } from "ui/shared/CharacterCounter";
import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";
import { Switch } from "ui/shared/Switch";
import { object, string } from "yup";

import { ExternalPortals } from "../components/Sidebar/Search/ExternalPortalList/ExternalPortals";
import { useStore } from "../lib/store";
import { FlowInformation } from "../utils";

interface ReadMePageProps {
flowInformation: FlowInformation;
teamSlug: string;
}

interface ReadMePageForm {
serviceSummary: string;
serviceDescription: string;
serviceLimitations: string;
}
import { ReadMePageForm, ReadMePageProps } from "./types";

export const ReadMePage: React.FC<ReadMePageProps> = ({
flowInformation,
teamSlug,
flowSlug,
}) => {
const { status: flowStatus } = flowInformation;
const [
Expand All @@ -44,6 +37,7 @@ export const ReadMePage: React.FC<ReadMePageProps> = ({
updateFlowSummary,
flowLimitations,
updateFlowLimitations,
externalPortals,
flowName,
] = useStore((state) => [
state.flowDescription,
Expand All @@ -52,11 +46,16 @@ export const ReadMePage: React.FC<ReadMePageProps> = ({
state.updateFlowSummary,
state.flowLimitations,
state.updateFlowLimitations,
state.externalPortals,
state.flowName,
]);

const toast = useToast();

const hasExternalPortals = Boolean(Object.keys(externalPortals).length);

const [showExternalPortals, setShowExternalPortals] = useState(false);

const formik = useFormik<ReadMePageForm>({
initialValues: {
serviceSummary: flowSummary || "",
Expand All @@ -66,14 +65,14 @@ export const ReadMePage: React.FC<ReadMePageProps> = ({
onSubmit: async (values, { setSubmitting, setFieldError }) => {
try {
const updateFlowDescriptionPromise = updateFlowDescription(
values.serviceDescription
values.serviceDescription,
);
const updateFlowSummaryPromise = updateFlowSummary(
values.serviceSummary
values.serviceSummary,
);

const updateFlowLimitationsPromise = updateFlowLimitations(
values.serviceLimitations
values.serviceLimitations,
);

const [descriptionResult, summaryResult, limitationsResult] =
Expand All @@ -89,27 +88,27 @@ export const ReadMePage: React.FC<ReadMePageProps> = ({
if (!descriptionResult) {
setFieldError(
"serviceDescription",
"Unable to update the flow description. Please try again."
"Unable to update the flow description. Please try again.",
);
}
if (!summaryResult) {
setFieldError(
"serviceSummary",
"Unable to update the service summary. Please try again."
"Unable to update the service summary. Please try again.",
);
}
if (!limitationsResult) {
setFieldError(
"serviceLimitations",
"Unable to update the service limitations. Please try again."
"Unable to update the service limitations. Please try again.",
);
}
throw new Error("One or more updates failed");
}
} catch (error) {
console.error("Error updating descriptions:", error);
toast.error(
"An error occurred while updating descriptions. Please try again."
"An error occurred while updating descriptions. Please try again.",
);
} finally {
setSubmitting(false);
Expand All @@ -120,7 +119,7 @@ export const ReadMePage: React.FC<ReadMePageProps> = ({
validationSchema: object({
serviceSummary: string().max(
120,
"Service description must be 120 characters or less"
"Service description must be 120 characters or less",
),
}),
});
Expand All @@ -129,7 +128,8 @@ export const ReadMePage: React.FC<ReadMePageProps> = ({
<Container maxWidth="formWrap">
<SettingsSection>
<Typography variant="h2" component="h3" gutterBottom>
{flowName}
{/* fallback from request params if store not populated with flowName */}
{flowName || capitalize(flowSlug.replaceAll("-", " "))}
</Typography>

<Box display={"flex"}>
Expand Down Expand Up @@ -161,6 +161,7 @@ export const ReadMePage: React.FC<ReadMePageProps> = ({
disabled={!useStore.getState().canUserEditTeam(teamSlug)}
inputProps={{
"aria-describedby": "A short blurb on what this service is.",
"aria-label": "Service Description",
}}
/>
<CharacterCounter
Expand Down Expand Up @@ -235,6 +236,27 @@ export const ReadMePage: React.FC<ReadMePageProps> = ({
</Box>
</form>
</SettingsSection>
<Box pt={2}>
<Switch
label={"Show external portals"}
name={"service.status"}
variant="editorPage"
checked={showExternalPortals}
onChange={() => setShowExternalPortals(!showExternalPortals)}
/>
{showExternalPortals &&
(hasExternalPortals ? (
<Box pt={2} data-testid="searchExternalPortalList">
<InputLegend>External Portals</InputLegend>
<Typography variant="body1" my={2}>
Your service contains the following external portals:
</Typography>
<ExternalPortals externalPortals={externalPortals} />
</Box>
) : (
<Typography>This service has no external portals.</Typography>
))}
</Box>
</Container>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { act, screen } from "@testing-library/react";
import { FullStore, useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { setup } from "testUtils";
import { axe } from "vitest-axe";

import { ReadMePage } from "../ReadMePage";
import { defaultProps, longInput, platformAdminUser } from "./helpers";

const { getState, setState } = useStore;

let initialState: FullStore;

describe("Read Me Page component", () => {
beforeAll(() => (initialState = getState()));

beforeEach(() => {
getState().setUser(platformAdminUser);
});

afterEach(() => {
act(() => setState(initialState));
});

it("renders and submits data without an error", async () => {
const { user } = setup(
<DndProvider backend={HTML5Backend}>
<ReadMePage {...defaultProps} />
</DndProvider>
);

expect(getState().flowSummary).toBe("");

const serviceSummaryInput = screen.getByPlaceholderText("Description");

await user.type(serviceSummaryInput, "a summary");

await user.click(screen.getByRole("button", { name: "Save" }));

expect(screen.getByText("a summary")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Reset changes" })); // refreshes page and refetches data

expect(getState().flowSummary).toEqual("a summary");
expect(screen.getByText("a summary")).toBeInTheDocument();
});

it("displays an error if the service description is longer than 120 characters", async () => {
const { user } = setup(
<DndProvider backend={HTML5Backend}>
<ReadMePage {...defaultProps} />
</DndProvider>
);

expect(getState().flowSummary).toBe("");

const serviceSummaryInput = screen.getByPlaceholderText("Description");

await user.type(serviceSummaryInput, longInput);

expect(
await screen.findByText("You have 2 characters too many")
).toBeInTheDocument();

await user.click(screen.getByRole("button", { name: "Save" }));

expect(
screen.getByText("Service description must be 120 characters or less")
).toBeInTheDocument();

await user.click(screen.getByRole("button", { name: "Reset changes" })); // refreshes page and refetches data
expect(getState().flowSummary).toBe(""); // db has not been updated
});

it("displays data in the fields if there is already flow information in the database", async () => {
await act(async () =>
setState({
flowSummary: "This flow summary is in the db already",
})
);

setup(
<DndProvider backend={HTML5Backend}>
<ReadMePage {...defaultProps} />
</DndProvider>
);

expect(
screen.getByText("This flow summary is in the db already")
).toBeInTheDocument();
});

it.todo("displays an error toast if there is a server-side issue"); // waiting for PR 4019 to merge first so can use msw package

it("should not have any accessibility violations", async () => {
const { container } = setup(
<DndProvider backend={HTML5Backend}>
<ReadMePage {...defaultProps} />
</DndProvider>
);

const results = await axe(container);
expect(results).toHaveNoViolations();
});

it("is not editable if the user has the teamViewer role", async () => {
const teamViewerUser = { ...platformAdminUser, isPlatformAdmin: false };
getState().setUser(teamViewerUser);

getState().setTeamMembers([{ ...teamViewerUser, role: "teamViewer" }]);

setup(
<DndProvider backend={HTML5Backend}>
<ReadMePage {...defaultProps} />
</DndProvider>
);

expect(getState().flowSummary).toBe("");

const serviceSummaryInput = screen.getByPlaceholderText("Description");

expect(serviceSummaryInput).toBeDisabled();
expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
});
});
26 changes: 26 additions & 0 deletions editor.planx.uk/src/pages/FlowEditor/ReadMePage/tests/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ReadMePageProps } from "../types";

export const platformAdminUser = {
id: 1,
firstName: "Editor",
lastName: "Test",
isPlatformAdmin: true,
email: "[email protected]",
teams: [],
jwt: "x.y.z",
};

export const defaultProps = {
flowSlug: "apply-for-planning-permission",
teamSlug: "barnet",
flowInformation: {
status: "online",
description: "A long description of a service",
summary: "A short blurb",
limitations: "",
settings: {},
},
} as ReadMePageProps;

export const longInput =
"A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my who"; // 122 characters
13 changes: 13 additions & 0 deletions editor.planx.uk/src/pages/FlowEditor/ReadMePage/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { FlowInformation } from "../utils";

export interface ReadMePageProps {
flowInformation: FlowInformation;
teamSlug: string;
flowSlug: string;
}

export interface ReadMePageForm {
serviceSummary: string;
serviceDescription: string;
serviceLimitations: string;
}
Loading

0 comments on commit f239666

Please sign in to comment.