Enable file upload form

Enable error boundary to catch React errors (#7)
Fix warning for link noreferrer
Include authorization header with file upload
This commit is contained in:
John Wu
2021-01-20 13:36:40 -08:00
committed by GitHub
parent ec4dd7d35d
commit fcde299197
9 changed files with 43 additions and 24 deletions

View File

@@ -24,10 +24,10 @@ export const FileUploadProvider = ({ children }) => {
done(); done();
}; };
const upload = async (files) => { const upload = async (files, accessToken) => {
try { try {
if (!files || files.length === 0) throw new Error("No file provided"); if (!files || files.length === 0) throw new Error("File required");
if (!accessToken || accessToken.length === 0) throw new Error("Access token required")
const file = files[0].file; const file = files[0].file;
const filename = file.name; const filename = file.name;
@@ -37,7 +37,7 @@ export const FileUploadProvider = ({ children }) => {
setStatus(`Uploading ${filename}`); setStatus(`Uploading ${filename}`);
setCancelUpload(getCancelToken()); setCancelUpload(getCancelToken());
const { data } = await uploadFile(file, setProgress, cancelUpload); const { data } = await uploadFile(file, accessToken, setProgress, cancelUpload);
if (data.message) throw new Error(`${data.error}. ${data.message}`); if (data.message) throw new Error(`${data.error}. ${data.message}`);
const url = ((data && data.link) ? data.link : "No URL available"); const url = ((data && data.link) ? data.link : "No URL available");
setLinkURL(url); setLinkURL(url);

View File

@@ -1,6 +1,6 @@
jest.mock("../../services/uploadFile"); jest.mock("../../services/uploadFile");
import {uploadFile, getCancelToken, setUploadFileResponse, setUploadFileDelay, getIssuedCancelToken } from "../../services/uploadFile" import { setUploadFileDelay } from "../../services/uploadFile"
import { FileUploadProvider, useFileUploadContext } from "../Contexts/FileUploadContext"; import { FileUploadProvider, useFileUploadContext } from "../Contexts/FileUploadContext";
import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react" import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react"
@@ -9,6 +9,8 @@ describe("FileUploadContext", () => {
beforeEach(() => { beforeEach(() => {
const TestComp = () => { const TestComp = () => {
const { progress, uploading, status, linkURL, upload, cancel } = useFileUploadContext(); const { progress, uploading, status, linkURL, upload, cancel } = useFileUploadContext();
const TEST_FILE = [{ file: { name: "test.jpg" }}];
const TEST_ACCESSTOKEN = "ACCESSTOKEN";
return ( return (
<> <>
<div data-testid="uploading">{uploading.toString()}</div> <div data-testid="uploading">{uploading.toString()}</div>
@@ -16,7 +18,8 @@ describe("FileUploadContext", () => {
<div data-testid="status">{status}</div> <div data-testid="status">{status}</div>
<div data-testid="linkURL">{linkURL}</div> <div data-testid="linkURL">{linkURL}</div>
<button data-testid="uploadNoFile" onClick={() => upload()}/> <button data-testid="uploadNoFile" onClick={() => upload()}/>
<button data-testid="upload" onClick={() => upload([{ file: { name: "test.jpg" }}])}/> <button data-testid="uploadNoToken" onClick={() => upload(TEST_FILE)}/>
<button data-testid="upload" onClick={() => upload(TEST_FILE, TEST_ACCESSTOKEN)}/>
<button data-testid="cancel" onClick={() => cancel()}/> <button data-testid="cancel" onClick={() => cancel()}/>
</> </>
); );
@@ -39,7 +42,15 @@ describe("FileUploadContext", () => {
fireEvent.click(screen.getByTestId("uploadNoFile")); fireEvent.click(screen.getByTestId("uploadNoFile"));
expect(screen.getByTestId("uploading").innerHTML).toEqual("false"); expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
expect(screen.getByTestId("progress").innerHTML).toEqual("-1"); expect(screen.getByTestId("progress").innerHTML).toEqual("-1");
expect(screen.getByTestId("status").innerHTML).toEqual("Error occured: No file provided"); expect(screen.getByTestId("status").innerHTML).toEqual("Error occured: File required");
expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
})
it("Upload no access token", async () => {
fireEvent.click(screen.getByTestId("uploadNoToken"));
expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
expect(screen.getByTestId("progress").innerHTML).toEqual("-1");
expect(screen.getByTestId("status").innerHTML).toEqual("Error occured: Access token required");
expect(screen.getByTestId("linkURL").innerHTML).toEqual(""); expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
}) })

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Typography } from '@material-ui/core';
export default class ErrorBoundary extends Component { export default class ErrorBoundary extends Component {
state = { state = {
@@ -14,7 +15,7 @@ export default class ErrorBoundary extends Component {
this.setState({ errorInfo }); this.setState({ errorInfo });
} }
render() { render() {
if (this.state.hasError) return (<h1>Oops. An Error Occured</h1>); if (this.state.hasError) return (<Typography variant="h3" align="center" >Oops. An React JS Error Occured.</Typography>);
return this.props.children; return this.props.children;
} }
} }

View File

@@ -1,13 +1,15 @@
jest.mock("../Contexts/UserContext"); jest.mock("../Contexts/UserContext");
jest.mock("../Contexts/FileUploadContext"); jest.mock("../Contexts/FileUploadContext");
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from "react-router-dom";
import { render, cleanup } from "@testing-library/react" import { render, cleanup } from "@testing-library/react";
import FileUploadForm from './index'; import FileUploadForm from "./index";
import { setToken } from "../Contexts/UserContext";
describe("File Upload Form", () => { describe("File Upload Form", () => {
it("Should render", () => { it("Should render", () => {
setToken({ accessToken: { jwtToken: "TEST" } });
const { container } = render(<BrowserRouter><FileUploadForm /></BrowserRouter>); const { container } = render(<BrowserRouter><FileUploadForm /></BrowserRouter>);
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
cleanup(); cleanup();

View File

@@ -6,17 +6,18 @@ import { useFileUploadContext, FileUploadProvider } from "../Contexts/FileUploa
import ModalProgressBar from "../ModalProgressBar"; import ModalProgressBar from "../ModalProgressBar";
import useStyles from "../Styles"; import useStyles from "../Styles";
const FileUploadZone = ({ classes }) => { const FileUploadZone = ({ classes, token }) => {
const { uploading, progress, status, linkURL, upload, cancel } = useFileUploadContext(); const { upload } = useFileUploadContext();
const { token: { accessToken: { jwtToken : authToken } } } = useUserContext();
return ( return (
<form className={classes.form} noValidate> <form className={classes.form} noValidate>
<DropzoneAreaBase <DropzoneAreaBase
maxFileSize={5e+7} maxFileSize={5e+7}
showAlerts={false} showAlerts={false}
onAdd={upload} onAdd={(files) => upload(files, authToken)}
/> />
<ModalProgressBar uploading={uploading} progress={progress} onCancel={cancel} status={status} linkURL={linkURL} /> <ModalProgressBar />
</form> </form>
); );
}; };

View File

@@ -2,6 +2,7 @@ import React from "react";
import Modal from '@material-ui/core/Modal'; import Modal from '@material-ui/core/Modal';
import { Button, LinearProgress } from "@material-ui/core"; import { Button, LinearProgress } from "@material-ui/core";
import { useFileUploadContext } from "../Contexts/FileUploadContext";
const getModalStyle = () => { const getModalStyle = () => {
const top = 30; const top = 30;
@@ -21,17 +22,17 @@ const getModalStyle = () => {
}; };
}; };
const ModalProgressBar = ({ onCancel, uploading, progress, status, linkURL }) => { const ModalProgressBar = () => {
const { uploading, progress, status, linkURL, cancel } = useFileUploadContext();
const modalStyle = getModalStyle(); const modalStyle = getModalStyle();
const onClickCancel = () => { const onClickCancel = cancel;
if (onCancel) onCancel();
}
return ( return (
<Modal open={uploading}> <Modal open={uploading}>
<div style={modalStyle}> <div style={modalStyle}>
{status && <p>{status}</p>} {status && <p>{status}</p>}
{linkURL && <p><a href={linkURL} target="_blank">View</a></p>} {linkURL && <p><a href={linkURL} target="_blank" rel="noreferrer">View</a></p>}
<LinearProgress variant="determinate" value={progress} /> <LinearProgress variant="determinate" value={progress} />
<Button onClick={onClickCancel}> <Button onClick={onClickCancel}>
{ progress === 100 || progress === -1 ? "Done" : "Cancel" } { progress === 100 || progress === -1 ? "Done" : "Cancel" }

View File

@@ -2,12 +2,14 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './index.css'; import './index.css';
import App from './components/App'; import App from './components/App';
// import ErrorBoundary from './components/ErrorBoundary'; import ErrorBoundary from './components/ErrorBoundary';
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals';
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<ErrorBoundary>
<App /> <App />
</ErrorBoundary>
</React.StrictMode>, </React.StrictMode>,
document.getElementById('root') document.getElementById('root')
); );

View File

@@ -11,7 +11,7 @@ export const getCancelToken = () => {
return issuedCancelToken; return issuedCancelToken;
} }
export const uploadFile = async (file, onProgress, cancelToken) => { export const uploadFile = async (file, token, onProgress, cancelToken) => {
if (!uploadFileDelay) return uploadFileResponse; if (!uploadFileDelay) return uploadFileResponse;
onProgress(50); onProgress(50);
await delay(10000); await delay(10000);

View File

@@ -7,12 +7,13 @@ export const getCancelToken = () => {
return token.source(); return token.source();
} }
export const uploadFile = (file, onProgress, cancelToken) => { export const uploadFile = (file, token, onProgress, cancelToken) => {
const form = new FormData(); const form = new FormData();
let options = { let options = {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${token}`,
}, },
cancelToken, cancelToken,
}; };