diff --git a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Modal.tsx b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Modal.tsx index b346d906c9..16db6595ec 100644 --- a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Modal.tsx +++ b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Modal.tsx @@ -35,6 +35,7 @@ import { fileLabelSchema } from "./schema"; interface FileTaggingModalProps { uploadedFiles: FileUploadSlot[]; fileList: FileList; + orderByMostRecentlyUploaded: boolean; setFileList: (value: React.SetStateAction) => void; setShowModal: (value: React.SetStateAction) => void; } @@ -52,6 +53,7 @@ const ListHeader = styled(Box)(({ theme }) => ({ export const FileTaggingModal = ({ uploadedFiles, fileList, + orderByMostRecentlyUploaded, setFileList, setShowModal, }: FileTaggingModalProps) => { @@ -71,6 +73,22 @@ export const FileTaggingModal = ({ .catch((err) => setError(err.message)); }; + const returnUploadedFileCardsAndSelects = () => { + const uploadedFileCardsAndSelects = uploadedFiles.map((slot) => ( + + + + + )); + return orderByMostRecentlyUploaded + ? uploadedFileCardsAndSelects.reverse() + : uploadedFileCardsAndSelects; + }; + return ( - {uploadedFiles.map((slot) => ( - - - - - ))} + {returnUploadedFileCardsAndSelects()} { ); expect(error).toBeVisible(); }); + + test("Uploaded file cards are displayed in most recently uploaded first", async () => { + const handleSubmit = jest.fn(); + const { user } = setup( + , + ); + + // No file requirements have been satisfied yet + let incompleteIcons = screen.getAllByTestId("incomplete-icon"); + expect(incompleteIcons).toHaveLength(2); + + // Upload first file + mockedAxios.post.mockResolvedValue({ + data: { + file_type: "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]); + + // Modal opened automatically + const fileTaggingModal = await within(document.body).findByTestId( + "file-tagging-dialog", + ); + + // The number of selects in the modal matches the number of uploaded files + const selects = await within(document.body).findAllByTestId("select"); + expect(selects).toHaveLength(1); + + const submitModalButton = await within(fileTaggingModal).findByText("Done"); + expect(submitModalButton).toBeVisible(); + + // Apply tag to this file + fireEvent.change(selects[0], { target: { value: "Roof plan" } }); + + // Close modal successfully + user.click(submitModalButton); + await waitFor(() => expect(fileTaggingModal).not.toBeVisible()); + + // Uploaded file displayed as card with chip tags + expect(screen.getByText("test1.png")).toBeVisible(); + const chips = screen.getAllByTestId("uploaded-file-chip"); + expect(chips).toHaveLength(1); + expect(chips[0]).toHaveTextContent("Roof plan"); + + // Requirements list reflects successfully tagged uploads + const completeIcons = screen.getAllByTestId("complete-icon"); + expect(completeIcons).toHaveLength(1); + incompleteIcons = screen.getAllByTestId("incomplete-icon"); + expect(incompleteIcons).toHaveLength(1); + + // Upload second file + mockedAxios.post.mockResolvedValue({ + data: { + file_type: "image/png", + fileUrl: "https://api.editor.planx.dev/file/private/gws7l5d1/test2.jpg", + }, + }); + + const file2 = new File(["test2"], "test2.png", { type: "image/png" }); + const theInput = screen.getByTestId("upload-input"); + await user.upload(theInput, [file2]); + + const theModal = await within(document.body).findByTestId( + "file-tagging-dialog", + ); + expect(theModal).toBeVisible(); + + const modalUploadedFileCards = await within(theModal).getAllByTestId( + "uploaded-file-card", + ); + expect(modalUploadedFileCards[0]).toHaveTextContent("test2.png"); + expect(modalUploadedFileCards[1]).toHaveTextContent("test1.png"); + + const newSelects = await within(document.body).findAllByTestId("select"); + expect(newSelects).toHaveLength(2); + + const newSubmitModalButton = await within(theModal).findByText("Done"); + expect(newSubmitModalButton).toBeVisible(); + + // Apply tag to second file + fireEvent.change(newSelects[0], { + target: { value: "Heritage statement" }, + }); + + // Close modal successfully + user.click(newSubmitModalButton); + await waitFor(() => expect(theModal).not.toBeVisible()); + + // Second uploaded file displayed + expect(screen.getByText("test2.png")).toBeVisible(); + + // Has expected chips in expected order + const newChips = screen.getAllByTestId("uploaded-file-chip"); + expect(newChips).toHaveLength(2); + expect(newChips[0]).toHaveTextContent("Heritage statement"); + expect(newChips[1]).toHaveTextContent("Roof plan"); + + // Has expected file cards in expected order + const fileCards = screen.getAllByTestId("uploaded-file-card"); + expect(fileCards[0]).toHaveTextContent("test2.png"); + expect(fileCards[1]).toHaveTextContent("test1.png"); + + // Requirements list reflects successfully tagged uploads + const theCompleteIcons = screen.getAllByTestId("complete-icon"); + expect(theCompleteIcons).toHaveLength(2); + + // "Continue" onto to the next node + expect(screen.getByText("Continue")).toBeEnabled(); + await user.click(screen.getByText("Continue")); + expect(handleSubmit).toHaveBeenCalledTimes(1); + }); }); describe("Error handling", () => { diff --git a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Public.tsx b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Public.tsx index 6ef543884e..07b1517251 100644 --- a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Public.tsx +++ b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Public.tsx @@ -89,6 +89,7 @@ function Component(props: Props) { }, []); const [slots, setSlots] = useState([]); + const orderByMostRecentlyUploaded = true; // Track number of slots, and open modal when this increases const previousSlotCount = usePrevious(slots.length); @@ -147,6 +148,29 @@ function Component(props: Props) { } }; + const returnUploadedFileCards = () => { + const uploadedFileCards = slots.map((slot) => { + return ( + { + setSlots( + slots.filter((currentSlot) => currentSlot.file !== slot.file), + ); + setFileUploadStatus(`${slot.file.path} was deleted`); + removeSlots(getTagsForSlot(slot.id, fileList), slot, fileList); + }} + /> + ); + }); + return orderByMostRecentlyUploaded + ? uploadedFileCards.reverse() + : uploadedFileCards; + }; + return ( )} - {slots.map((slot) => { - return ( - { - setSlots( - slots.filter( - (currentSlot) => currentSlot.file !== slot.file, - ), - ); - setFileUploadStatus(`${slot.file.path} was deleted`); - removeSlots( - getTagsForSlot(slot.id, fileList), - slot, - fileList, - ); - }} - /> - ); - })} + {returnUploadedFileCards()} diff --git a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/model.ts b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/model.ts index 5b24dea4e9..61f57e9b9d 100644 --- a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/model.ts +++ b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/model.ts @@ -335,7 +335,6 @@ export const addOrAppendSlots = ( const categories = Object.keys(updatedFileList) as Array< keyof typeof updatedFileList >; - tags.forEach((tag) => { categories.forEach((category) => { const index = updatedFileList[category].findIndex( @@ -359,7 +358,6 @@ export const addOrAppendSlots = ( } }); }); - return updatedFileList; }; diff --git a/editor.planx.uk/src/@planx/components/shared/PrivateFileUpload/UploadedFileCard.tsx b/editor.planx.uk/src/@planx/components/shared/PrivateFileUpload/UploadedFileCard.tsx index 14d92d2a63..47748625e7 100644 --- a/editor.planx.uk/src/@planx/components/shared/PrivateFileUpload/UploadedFileCard.tsx +++ b/editor.planx.uk/src/@planx/components/shared/PrivateFileUpload/UploadedFileCard.tsx @@ -92,7 +92,7 @@ export const UploadedFileCard: React.FC = ({ tags, }) => ( - +