Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add _requestedFiles key to Passport #2763

Merged
merged 7 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function Component(props: any) {
</InputRow>
<InputRow>
<Input
// required
required
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this should be correct behavior - but I wonder if this will break any discretionary submission services like building control forms or something? Hopefully not / easy enough to fix forward if we find it does I guess? I don't necessarily think we should be putting as much proactive effort into find/replace etc outside of "standard" services?

format="data"
name="fn"
value={formik.values.fn}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Story = StoryObj<typeof meta>;

export const Basic = {
args: {
fn: "roofPlan",
title: "Upload roof plan",
description:
"The plan should show the roof of the building as it looks today.",
Expand Down
31 changes: 8 additions & 23 deletions editor.planx.uk/src/@planx/components/FileUpload/Public.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,31 @@ import { uniqueId } from "lodash";
import React from "react";
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 () => {
const handleSubmit = jest.fn();

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

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

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

test("recovers previously submitted files when clicking the back button", async () => {
const handleSubmit = jest.fn();
const componentId = uniqueId();
const uploadedFile = {
data: {
[componentId]: [dummyFile],
},
};

const { user } = setup(
<FileUpload
id={componentId}
handleSubmit={handleSubmit}
previouslySubmittedData={uploadedFile}
/>,
);

await user.click(screen.getByTestId("continue-button"));

expect(handleSubmit).toHaveBeenCalledWith(uploadedFile);
});

test("recovers previously submitted files when clicking the back button even if a data field is set", async () => {
const handleSubmit = jest.fn();
const componentId = uniqueId();
const dataField = "data-field";
const uploadedFile = {
data: {
[dataField]: [dummyFile],
[PASSPORT_REQUESTED_FILES_KEY]: {
required: [dataField],
recommended: [],
optional: [],
},
},
};

Expand Down Expand Up @@ -85,6 +69,7 @@ it("should not have any accessibility violations", async () => {

const { container } = setup(
<FileUpload
fn="someKey"
id={componentId}
handleSubmit={handleSubmit}
description="description"
Expand Down
67 changes: 48 additions & 19 deletions editor.planx.uk/src/@planx/components/FileUpload/Public.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { MoreInformation } from "@planx/components/shared";
import Card from "@planx/components/shared/Preview/Card";
import QuestionHeader from "@planx/components/shared/Preview/QuestionHeader";
import { Store } from "pages/FlowEditor/lib/store";
import { Store, useStore } from "pages/FlowEditor/lib/store";
import type { handleSubmit } from "pages/Preview/Node";
import React, { useEffect, useRef, useState } from "react";
import { FileWithPath } from "react-dropzone";
import ErrorWrapper from "ui/shared/ErrorWrapper";
import { array } from "yup";

import { PASSPORT_REQUESTED_FILES_KEY } from "../FileUploadAndLabel/model";
import { PrivateFileUpload } from "../shared/PrivateFileUpload/PrivateFileUpload";
import { getPreviouslySubmittedData, makeData } from "../shared/utils";

interface Props extends MoreInformation {
id?: string;
title?: string;
fn?: string;
fn: string;
description?: string;
handleSubmit: handleSubmit;
previouslySubmittedData?: Store.userData;
Expand Down Expand Up @@ -50,27 +51,55 @@ const FileUpload: React.FC<Props> = (props) => {
const [slots, setSlots] = useState<FileUploadSlot[]>(recoveredSlots ?? []);
const [validationError, setValidationError] = useState<string>();

const uploadedFiles = (slots: FileUploadSlot[]) =>
makeData(
props,
slots.map((slot) => ({
url: slot.url,
filename: slot.file.path,
cachedSlot: {
...slot,
file: {
path: slot.file.path,
type: slot.file.type,
size: slot.file.size,
},
},
})),
);

const updatedRequestedFiles = () => {
// const { required, recommended, optional } = useStore
// .getState()
// .requestedFiles();

const { required, recommended, optional } = useStore
.getState()
.computePassport().data?.[PASSPORT_REQUESTED_FILES_KEY] || {
required: [],
recommended: [],
optional: [],
};

return {
[PASSPORT_REQUESTED_FILES_KEY]: {
required: [...required, props.fn],
recommended,
optional,
},
};
};

const handleSubmit = () => {
slotsSchema
.validate(slots)
.then(() => {
props.handleSubmit(
makeData(
props,
slots.map((slot) => ({
url: slot.url,
filename: slot.file.path,
cachedSlot: {
...slot,
file: {
path: slot.file.path,
type: slot.file.type,
size: slot.file.size,
},
},
})),
),
);
props.handleSubmit({
data: {
...uploadedFiles(slots).data,
...updatedRequestedFiles(),
},
});
})
.catch((err) => {
setValidationError(err.message);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { fireEvent, screen, waitFor, within } from "@testing-library/react";
import {
act,
fireEvent,
screen,
waitFor,
within,
} from "@testing-library/react";
import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
import axios from "axios";
import { vanillaStore } from "pages/FlowEditor/lib/store";
import { FullStore, useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { axe, setup } from "testUtils";
import { Breadcrumbs } from "types";

import { mockFileTypes, mockFileTypesUniqueKeys } from "./mocks";
import { Condition, PASSPORT_REQUESTED_FILES_KEY } from "./model";
import FileUploadAndLabelComponent from "./Public";

const { getState, setState } = vanillaStore;
let initialState: FullStore;

jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;

Expand Down Expand Up @@ -595,3 +609,165 @@ describe("Error handling", () => {
expect(handleSubmit).not.toHaveBeenCalled();
});
});

describe("Submitting data", () => {
beforeAll(() => (initialState = getState()));

afterEach(() => waitFor(() => setState(initialState)));

it("records the user uploaded files", async () => {
const handleSubmit = jest.fn();
const { user } = setup(
<FileUploadAndLabelComponent
title="Test title"
handleSubmit={handleSubmit}
fileTypes={mockFileTypesUniqueKeys}
/>,
);

await uploadAndTagSingleFile(user);

await user.click(screen.getByText("Continue"));
expect(handleSubmit).toHaveBeenCalledTimes(1);

const submitted = handleSubmit.mock.calls[0][0];
const uploadedFile = submitted?.data?.roofPlan;

expect(uploadedFile).toBeDefined();
expect(uploadedFile).toHaveLength(1);
expect(uploadedFile[0]).toEqual(
expect.objectContaining({
filename: "test1.png",
url: "https://api.editor.planx.dev/file/private/gws7l5d1/test1.jpg",
}),
);
});

it("records the full file type list presented to the user", async () => {
const handleSubmit = jest.fn();
const { user } = setup(
<FileUploadAndLabelComponent
title="Test title"
handleSubmit={handleSubmit}
fileTypes={mockFileTypesUniqueKeys}
/>,
);

await uploadAndTagSingleFile(user);
await user.click(screen.getByText("Continue"));

const submitted = handleSubmit.mock.calls[0][0];
const requestedFiles = submitted?.data?._requestedFiles;

expect(requestedFiles).toBeDefined();
expect(requestedFiles.required).toContain("roofPlan");
expect(requestedFiles.recommended).toContain("heritage");
expect(requestedFiles.optional).toContain("utilityBill");
});

it("appends to the list of existing requested files", async () => {
// Mimic having passed file upload / file upload and label component
const breadcrumbs: Breadcrumbs = {
previousFileUploadComponent: {
auto: false,
data: {
anotherFileType: [
{
url: "http://test.com/file.jpg",
filename: "file.jpg",
},
],
[PASSPORT_REQUESTED_FILES_KEY]: {
required: ["anotherFileType"],
recommended: [],
optional: [],
},
},
},
};

const flow = {
_root: {
edges: ["previousFileUploadComponent", "currentComponent"],
},
previousFileUploadComponent: {
data: {
fn: "anotherFileType",
color: "#EFEFEF",
},
type: 140,
},
currentComponent: {
type: 145,
data: {
title: "Upload and label",
fileTypes: [
{
name: "Floor Plan",
fn: "floorPlan",
rule: {
condition: "AlwaysRequired",
},
},
],
hideDropZone: false,
},
},
};

act(() => setState({ flow, breadcrumbs }));

const passport = useStore.getState().computePassport();
console.log({ passport });

const handleSubmit = jest.fn();
const { user } = setup(
<FileUploadAndLabelComponent
title="Test title"
handleSubmit={handleSubmit}
fileTypes={mockFileTypesUniqueKeys}
/>,
);

await uploadAndTagSingleFile(user);
await user.click(screen.getByText("Continue"));

const submitted = handleSubmit.mock.calls[0][0];
const requestedFiles = submitted?.data?._requestedFiles;

// Existing files from previous components
expect(requestedFiles.required).toContain("anotherFileType");

// Requested files from this component
expect(requestedFiles.required).toContain("roofPlan");
expect(requestedFiles.recommended).toContain("heritage");
expect(requestedFiles.optional).toContain("utilityBill");
});
});

/**
* Test helper which steps through the process of uploading and labelling a file
* Does not contain assertations - relies on these happening in other, more granular, passing tests
*/
const uploadAndTagSingleFile = async (user: UserEvent) => {
mockedAxios.post.mockResolvedValueOnce({
data: {
fileType: "image/png",
fileUrl: "https://api.editor.planx.dev/file/private/gws7l5d1/test1.jpg",
},
});

const file1 = new File(["test1"], "test1.png", { type: "image/png" });
const input = screen.getByTestId("upload-input");
await user.upload(input, [file1]);

const fileTaggingModal = await within(document.body).findByTestId(
"file-tagging-dialog",
);

const selects = await within(document.body).findAllByTestId("select");
fireEvent.change(selects[0], { target: { value: "Roof plan" } });

const submitModalButton = await within(fileTaggingModal).findByText("Done");
user.click(submitModalButton);
};
Loading
Loading