Skip to content

Commit

Permalink
always enable continue on file uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
jessicamcinchak committed May 10, 2024
1 parent 420c3c9 commit ea7046e
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,13 @@ test("shows the file upload option by default and requires user data to continue
// Navigate to upload a file screen
await user.click(screen.getByTestId("upload-file-button"));
expect(screen.getByText("Upload a file")).toBeInTheDocument();
expect(screen.getByTestId("continue-button")).toBeDisabled();

// Continue is enabled by default, but requires data to proceed
expect(screen.getByTestId("continue-button")).toBeEnabled();
await user.click(screen.getByTestId("continue-button"));
expect(
screen.getByTestId("error-message-upload-location-plan"),
).toBeInTheDocument();
});

test("hides the upload option and allows user to continue without drawing if editor specifies", async () => {
Expand Down
137 changes: 93 additions & 44 deletions editor.planx.uk/src/@planx/components/DrawBoundary/Public/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import { PrivateFileUpload } from "@planx/components/shared/PrivateFileUpload/Pr
import { squareMetresToHectares } from "@planx/components/shared/utils";
import type { PublicProps } from "@planx/components/ui";
import buffer from "@turf/buffer";
import { type Feature, point } from "@turf/helpers";
import { type Feature,point } from "@turf/helpers";
import { Store, useStore } from "pages/FlowEditor/lib/store";
import React, { useEffect, useRef, useState } from "react";
import { FONT_WEIGHT_SEMI_BOLD } from "theme";
import FullWidthWrapper from "ui/public/FullWidthWrapper";
import ErrorWrapper from "ui/shared/ErrorWrapper";
import { array } from "yup";

import {
DrawBoundary,
Expand All @@ -31,6 +33,21 @@ export type Props = PublicProps<DrawBoundary>;

export type Boundary = Feature | undefined;

const slotsSchema = array()
.required()
.test({
name: "nonUploading",
message: "Upload a location plan.",
test: (slots?: Array<FileUploadSlot>) => {
return Boolean(
slots &&
slots.length === 1 &&
!slots.some((slot) => slot.status === "uploading") &&
slots.every((slot) => slot.url && slot.status === "success"),
);
},
});

export default function Component(props: Props) {
const isMounted = useRef(false);
const passport = useStore((state) => state.computePassport());
Expand All @@ -52,7 +69,9 @@ export default function Component(props: Props) {
props.previouslySubmittedData?.data?.[PASSPORT_UPLOAD_KEY];
const startPage = previousFile ? "upload" : "draw";
const [page, setPage] = useState<"draw" | "upload">(startPage);

const [slots, setSlots] = useState<FileUploadSlot[]>(previousFile ?? []);
const [fileValidationError, setFileValidationError] = useState<string>();

const addressPoint =
passport?.data?._address?.longitude &&
Expand Down Expand Up @@ -94,44 +113,69 @@ export default function Component(props: Props) {
};
}, [page, setArea, setBoundary, setSlots]);

return (
<Card
handleSubmit={() => {
const newPassportData: Store.userData["data"] = {};
/**
* Declare a ref to hold a mutable copy the up-to-date validation error.
* The intention is to prevent frequent unnecessary update loops that clears the
* validation error state if it is already empty.
*/
const validationErrorRef = useRef(fileValidationError);
useEffect(() => {
validationErrorRef.current = fileValidationError;
}, [fileValidationError]);

useEffect(() => {
if (validationErrorRef.current) {
setFileValidationError(undefined);
}
}, [slots]);

// Used the map
if (page === "draw" && boundary && props.dataFieldBoundary) {
newPassportData[props.dataFieldBoundary] = boundary;
newPassportData[`${props.dataFieldBoundary}.buffered`] = buffer(
boundary,
bufferInMeters,
{ units: "meters" },
);
const validateAndSubmit = () => {
const newPassportData: Store.userData["data"] = {};

if (area && props.dataFieldArea) {
newPassportData[props.dataFieldArea] = area;
newPassportData[`${props.dataFieldArea}.hectares`] =
squareMetresToHectares(area);
}
// Used the map
if (page === "draw") {
if (boundary && props.dataFieldBoundary) {
newPassportData[props.dataFieldBoundary] = boundary;
newPassportData[`${props.dataFieldBoundary}.buffered`] = buffer(
boundary,
bufferInMeters,
{ units: "meters" },
);

// Track the type of map interaction
if (
boundary?.geometry ===
passport.data?.["property.boundary.title"]?.geometry
) {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Accept;
} else if (boundary?.properties?.dataset === "title-boundary") {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Amend;
} else {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Draw;
}
if (area && props.dataFieldArea) {
newPassportData[props.dataFieldArea] = area;
newPassportData[`${props.dataFieldArea}.hectares`] =
squareMetresToHectares(area);
}

// Track the type of map interaction
if (
boundary?.geometry ===
passport.data?.["property.boundary.title"]?.geometry
) {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Accept;
} else if (boundary?.properties?.dataset === "title-boundary") {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Amend;
} else {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Draw;
}

// Uploaded a file
if (page === "upload" && slots.length) {
props.handleSubmit?.({ data: { ...newPassportData } });
}

if (props.hideFileUpload && !boundary) {
props.handleSubmit?.({ data: { ...newPassportData } });
}
}

// Uploaded a file
if (page === "upload") {
slotsSchema
.validate(slots, { context: { slots } })
.then(() => {
newPassportData[PASSPORT_UPLOAD_KEY] = slots;
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Upload;
Expand All @@ -146,24 +190,27 @@ export default function Component(props: Props) {
recommended,
optional,
};
}

props.handleSubmit?.({ data: { ...newPassportData } });
}}
props.handleSubmit?.({ data: { ...newPassportData } });
})
.catch((err) => setFileValidationError(err?.message));
}
};

return (
<Card
handleSubmit={validateAndSubmit}
isValid={
props.hideFileUpload
? true
: Boolean(
(page === "draw" && boundary) ||
(page === "upload" && slots[0]?.url),
)
: Boolean((page === "draw" && boundary) || page === "upload")
}
>
{getBody(bufferInMeters)}
{getBody(bufferInMeters, fileValidationError)}
</Card>
);

function getBody(bufferInMeters: number) {
function getBody(bufferInMeters: number, fileValidationError?: string) {
if (page === "draw") {
return (
<>
Expand Down Expand Up @@ -257,7 +304,9 @@ export default function Component(props: Props) {
howMeasured={props.howMeasured}
definitionImg={props.definitionImg}
/>
<PrivateFileUpload slots={slots} setSlots={setSlots} maxFiles={1} />
<ErrorWrapper error={fileValidationError} id="upload-location-plan">
<PrivateFileUpload slots={slots} setSlots={setSlots} maxFiles={1} />
</ErrorWrapper>
<Box sx={{ textAlign: "right" }}>
<Link
component="button"
Expand Down
18 changes: 16 additions & 2 deletions editor.planx.uk/src/@planx/components/FileUpload/Public.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,30 @@ import { axe, setup } from "testUtils";
import { PASSPORT_REQUESTED_FILES_KEY } from "../FileUploadAndLabel/model";
import FileUpload from "./Public";

test("renders correctly and blocks submit if there are no files added", async () => {
test("renders correctly", async () => {
const handleSubmit = jest.fn();

setup(<FileUpload fn="someKey" handleSubmit={handleSubmit} />);

expect(screen.getByRole("button", { name: "Continue" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Continue" })).toBeEnabled();

expect(handleSubmit).toHaveBeenCalledTimes(0);
});

test("shows error if user tries to continue before adding files", async () => {
const handleSubmit = jest.fn();

const { user } = setup(
<FileUpload fn="elevations" id="elevations" handleSubmit={handleSubmit} />,
);

await user.click(screen.getByTestId("continue-button"));
expect(screen.getByText("Upload at least one file.")).toBeInTheDocument();

// Blocked by validation error
expect(handleSubmit).toHaveBeenCalledTimes(0);
});

test("recovers previously submitted files when clicking the back button", async () => {
const handleSubmit = jest.fn();
const componentId = uniqueId();
Expand Down
8 changes: 1 addition & 7 deletions editor.planx.uk/src/@planx/components/FileUpload/Public.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,7 @@ const FileUpload: React.FC<Props> = (props) => {
}, [slots]);

return (
<Card
isValid={
slots.length > 0 &&
slots.every((slot) => slot.url && slot.status === "success")
}
handleSubmit={handleSubmit}
>
<Card isValid={true} handleSubmit={handleSubmit}>
<QuestionHeader
title={props.title}
description={props.description}
Expand Down

0 comments on commit ea7046e

Please sign in to comment.