Skip to content

Commit

Permalink
feat: Reposition change button, add dialog on change (#2774)
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr authored Feb 12, 2024
1 parent 59aea24 commit 32a46ad
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 57 deletions.
117 changes: 113 additions & 4 deletions editor.planx.uk/src/@planx/components/Review/Public/Public.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { act, screen } from "@testing-library/react";
import { act, screen, waitFor, within } from "@testing-library/react";
import { FullStore, vanillaStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { axe, setup } from "testUtils";
Expand Down Expand Up @@ -34,6 +34,7 @@ beforeAll(() => (initialState = getState()));
describe("Simple flow", () => {
it("renders correctly", async () => {
const handleSubmit = jest.fn();
const changeAnswer = jest.fn();

const { user } = setup(
<Review
Expand All @@ -42,7 +43,7 @@ describe("Simple flow", () => {
flow={{}}
breadcrumbs={{}}
passport={{}}
changeAnswer={() => {}}
changeAnswer={changeAnswer}
handleSubmit={handleSubmit}
showChangeButton={true}
/>,
Expand All @@ -57,6 +58,7 @@ describe("Simple flow", () => {

it("doesn't return undefined when multiple nodes are filled", async () => {
const handleSubmit = jest.fn();
const changeAnswer = jest.fn();

setup(
<Review
Expand All @@ -65,7 +67,7 @@ describe("Simple flow", () => {
flow={mockedFlow}
breadcrumbs={mockedBreadcrumbs}
passport={mockedPassport}
changeAnswer={() => {}}
changeAnswer={changeAnswer}
handleSubmit={handleSubmit}
showChangeButton={true}
/>,
Expand All @@ -78,20 +80,127 @@ describe("Simple flow", () => {
});

it("should not have any accessibility violations", async () => {
const changeAnswer = jest.fn();

const { container } = setup(
<Review
title="Review"
description="Check your answers before submitting"
flow={mockedFlow}
breadcrumbs={mockedBreadcrumbs}
passport={mockedPassport}
changeAnswer={() => {}}
changeAnswer={changeAnswer}
showChangeButton={true}
/>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});

it("opens a 'confirm' dialog on change", async () => {
const handleSubmit = jest.fn();
const changeAnswer = jest.fn();

setup(
<Review
title="Review"
description="Check your answers before submitting"
flow={mockedFlow}
breadcrumbs={mockedBreadcrumbs}
passport={mockedPassport}
changeAnswer={changeAnswer}
handleSubmit={handleSubmit}
showChangeButton={true}
/>,
);

// Dialog not shown
expect(
within(document.body).queryByTestId("confirmation-dialog"),
).not.toBeInTheDocument();

// Click "change"
act(() =>
screen
.getAllByRole("button", { name: "Change Input a number" })[0]
.click(),
);

// Dialog shown to user
expect(
within(document.body).queryByTestId("confirmation-dialog"),
).toBeVisible();
});

it("selecting 'no' closes the dialog and does not make a change", async () => {
const handleSubmit = jest.fn();
const changeAnswer = jest.fn();

setup(
<Review
title="Review"
description="Check your answers before submitting"
flow={mockedFlow}
breadcrumbs={mockedBreadcrumbs}
passport={mockedPassport}
changeAnswer={changeAnswer}
handleSubmit={handleSubmit}
showChangeButton={true}
/>,
);

act(() =>
screen
.getAllByRole("button", { name: "Change Input a number" })[0]
.click(),
);
act(() => screen.getAllByRole("button", { name: "No" })[0].click());

// Modal closed
await waitFor(() =>
expect(
within(document.body).queryByTestId("confirmation-dialog"),
).not.toBeInTheDocument(),
);

// Change not made
expect(changeAnswer).not.toHaveBeenCalled();
});

it("selecting 'yes' closes the dialog and does make change", async () => {
const handleSubmit = jest.fn();
const changeAnswer = jest.fn();

setup(
<Review
title="Review"
description="Check your answers before submitting"
flow={mockedFlow}
breadcrumbs={mockedBreadcrumbs}
passport={mockedPassport}
changeAnswer={changeAnswer}
handleSubmit={handleSubmit}
showChangeButton={true}
/>,
);

act(() =>
screen
.getAllByRole("button", { name: "Change Input a number" })[0]
.click(),
);
act(() => screen.getAllByRole("button", { name: "Yes" })[0].click());

// Modal closed
await waitFor(() =>
expect(
within(document.body).queryByTestId("confirmation-dialog"),
).not.toBeInTheDocument(),
);

// Change made
expect(changeAnswer).toHaveBeenCalled();
});
});

describe("File uploads", () => {
Expand Down
118 changes: 65 additions & 53 deletions editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { visuallyHidden } from "@mui/utils";
import { PASSPORT_UPLOAD_KEY } from "@planx/components/DrawBoundary/model";
import { PASSPORT_REQUESTED_FILES_KEY } from "@planx/components/FileUploadAndLabel/model";
import { TYPES } from "@planx/components/types";
import { ConfirmationDialog } from "components/ConfirmationDialog";
import format from "date-fns/format";
import { useAnalyticsTracking } from "pages/FlowEditor/lib/analyticsProvider";
import { Store, useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import React, { useState } from "react";
import { FONT_WEIGHT_SEMI_BOLD } from "theme";

export default SummaryListsBySections;
Expand Down Expand Up @@ -114,8 +115,6 @@ function SummaryListsBySections(props: SummaryListsBySectionsProps) {
state.getSortedBreadcrumbsBySection,
]);

const { trackBackwardsNavigation } = useAnalyticsTracking();

const isValidComponent = ([nodeId, userData]: BreadcrumbEntry) => {
const node = props.flow[nodeId];
const doesNodeExist = Boolean(props.flow[nodeId]);
Expand Down Expand Up @@ -160,11 +159,6 @@ function SummaryListsBySections(props: SummaryListsBySectionsProps) {
.map(removeNonPresentationalNodes)
.map((section) => section.map(makeSummaryBreadcrumb));

const handleChangeAnswer = (id: string) => {
trackBackwardsNavigation("change", id);
props.changeAnswer(id);
};

return (
<>
{sectionsWithFilteredBreadcrumbs.map(
Expand All @@ -184,25 +178,13 @@ function SummaryListsBySections(props: SummaryListsBySectionsProps) {
>
{props.flow[`${Object.keys(sections[i])[0]}`]?.data?.title}
</Typography>
<Link
onClick={() =>
handleChangeAnswer(filteredBreadcrumbs[0].nodeId)
}
component="button"
fontSize="body1.fontSize"
>
Change
<span style={visuallyHidden}>
the answers in this section
</span>
</Link>
</Box>
<SummaryList
summaryBreadcrumbs={filteredBreadcrumbs}
flow={props.flow}
passport={props.passport}
changeAnswer={props.changeAnswer}
showChangeButton={false}
showChangeButton={props.showChangeButton}
/>
</React.Fragment>
),
Expand All @@ -229,42 +211,72 @@ function SummaryListsBySections(props: SummaryListsBySectionsProps) {
// ref https://design-system.service.gov.uk/components/summary-list/
function SummaryList(props: SummaryListProps) {
const { trackBackwardsNavigation } = useAnalyticsTracking();
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [nodeToChange, setNodeToChange] = useState<Store.nodeId | undefined>(
undefined,
);

const handleCloseDialog = (isConfirmed: boolean) => {
setIsDialogOpen(false);
if (isConfirmed && nodeToChange) {
trackBackwardsNavigation("change", nodeToChange);
props.changeAnswer(nodeToChange);
}
};

const handleChangeAnswer = (id: string) => {
trackBackwardsNavigation("change", id);
props.changeAnswer(id);
const handleChange = (nodeId: Store.nodeId) => {
setNodeToChange(nodeId);
setIsDialogOpen(true);
};

return (
<Grid showChangeButton={props.showChangeButton}>
{props.summaryBreadcrumbs.map(
({ component: Component, nodeId, node, userData }, i) => (
<React.Fragment key={i}>
<Component
nodeId={nodeId}
node={node}
userData={userData}
flow={props.flow}
passport={props.passport}
/>
{props.showChangeButton && (
<dd>
<Link
onClick={() => handleChangeAnswer(nodeId)}
component="button"
fontSize="body2.fontSize"
>
Change
<span style={visuallyHidden}>
{node.data?.title || node.data?.text || "this answer"}
</span>
</Link>
</dd>
)}
</React.Fragment>
),
)}
</Grid>
<>
<Grid showChangeButton={props.showChangeButton}>
{props.summaryBreadcrumbs.map(
({ component: Component, nodeId, node, userData }, i) => (
<React.Fragment key={i}>
<Component
nodeId={nodeId}
node={node}
userData={userData}
flow={props.flow}
passport={props.passport}
/>
{props.showChangeButton && (
<dd>
<Link
onClick={() => handleChange(nodeId)}
component="button"
fontSize="body2.fontSize"
>
Change
<span style={visuallyHidden}>
{node.data?.title || node.data?.text || "this answer"}
</span>
</Link>
</dd>
)}
</React.Fragment>
),
)}
</Grid>
<ConfirmationDialog
open={isDialogOpen}
onClose={handleCloseDialog}
title="Confirm"
confirmText="Yes"
cancelText="No"
>
<Typography>
If you change this answer, you’ll need to confirm all the other
answers after it. This is because a changed answer might mean we have
new or different questions to ask.
<br />
<br />
Are you sure you want to change your answer?
</Typography>
</ConfirmationDialog>
</>
);
}

Expand Down
54 changes: 54 additions & 0 deletions editor.planx.uk/src/components/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogTitle from "@mui/material/DialogTitle";
import React, { PropsWithChildren } from "react";

export interface ConfirmationDialogProps {
open: boolean;
onClose: (isConfirmed: boolean) => void;
title: string;
confirmText?: string;
cancelText?: string;
}

export const ConfirmationDialog: React.FC<
PropsWithChildren<ConfirmationDialogProps>
> = (props) => {
const {
onClose,
open,
children,
title,
confirmText = "Ok",
cancelText = "Cancel ",
} = props;

const onCancel = () => onClose(false);
const onConfirm = () => onClose(true);

return (
<Dialog
data-testid="confirmation-dialog"
PaperProps={{
sx: {
width: "100%",
maxWidth: (theme) => theme.breakpoints.values.md,
borderRadius: 0,
background: "#FFF",
margin: (theme) => theme.spacing(2),
},
}}
maxWidth="xl"
open={open}
>
<DialogTitle>{title}</DialogTitle>
<DialogContent dividers>{children}</DialogContent>
<DialogActions>
<Button onClick={onCancel}>{cancelText}</Button>
<Button onClick={onConfirm}>{confirmText}</Button>
</DialogActions>
</Dialog>
);
};

0 comments on commit 32a46ad

Please sign in to comment.