Skip to content

Commit 39fd196

Browse files
committed
Add full test coverage
1 parent 3495dd0 commit 39fd196

File tree

10 files changed

+6730
-1036
lines changed

10 files changed

+6730
-1036
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
node_modules
22
lib
3-
cjs
3+
cjs
4+
coverage
5+
.DS_Store

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This has been rewritten into a single file for simplicity, and the function signature has gotten much smaller.
44

5-
Todo: Test coverage
5+
100% Test Coverage :)
66

77
### What is it?
88

@@ -13,7 +13,7 @@ It has upload progress due to using XHR, and can be used for uploading file dire
1313
### Install
1414

1515
```js
16-
npm install react-use-upload@1.0.0-beta2
16+
npm install react-use-upload
1717
```
1818

1919
### Usage

babel.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
presets: [
3+
["@babel/preset-env", { targets: { node: "current" } }],
4+
"@babel/preset-typescript",
5+
],
6+
};

package-lock.json

Lines changed: 6462 additions & 1029 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "react-use-upload",
3-
"version": "1.0.0-beta2",
3+
"version": "1.0.0",
44
"description": "",
55
"main": "cjs/index.js",
66
"module": "lib/index.js",
77
"scripts": {
8-
"test": "tsc",
8+
"test": "jest",
99
"build": "npx babel src --out-dir lib --extensions \".ts,.tsx\" && npx babel --plugins @babel/plugin-transform-modules-commonjs src --out-dir cjs --extensions \".ts,.tsx\" && npm run create-types",
1010
"create-types": "tsc --emitDeclarationOnly && tsc --emitDeclarationOnly --outDir cjs",
1111
"watch": "npx babel --watch src --out-dir lib"
@@ -14,12 +14,28 @@
1414
"license": "ISC",
1515
"devDependencies": {
1616
"@babel/cli": "^7.13.0",
17-
"@babel/core": "^7.13.8",
17+
"@babel/core": "^7.13.10",
1818
"@babel/plugin-transform-modules-commonjs": "^7.13.8",
19+
"@babel/preset-env": "^7.13.10",
1920
"@babel/preset-react": "^7.12.13",
2021
"@babel/preset-typescript": "^7.13.0",
22+
"@testing-library/react": "^11.2.5",
23+
"@testing-library/user-event": "^12.8.3",
24+
"@types/jest": "^26.0.20",
2125
"@types/react": "^17.0.2",
26+
"babel-jest": "^26.6.3",
27+
"jest": "^26.6.3",
28+
"react": "^17.0.1",
29+
"react-dom": "^17.0.1",
2230
"typescript": "^4.2.3"
2331
},
24-
"dependencies": {}
32+
"dependencies": {},
33+
"jest": {
34+
"setupFiles": [
35+
"./tests/setup.js"
36+
],
37+
"collectCoverageFrom": [
38+
"./src/**/*.ts"
39+
]
40+
}
2541
}

tests/BasicTests.test.tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React from "react";
2+
import { screen, render, act } from "@testing-library/react";
3+
import { BasicTestUpload } from "./TestComponents";
4+
import userEvent from "@testing-library/user-event";
5+
6+
test("It uploads a file correctly", async () => {
7+
global.xhrOpen = jest.fn();
8+
global.xhrSend = jest.fn();
9+
render(<BasicTestUpload method="POST" url="github.gov" />);
10+
//loading is hidden
11+
expect(screen.queryByText("loading")).toEqual(null);
12+
13+
//upload a file into the input
14+
const file = new File(["hello"], "hello.png", { type: "image/png" });
15+
userEvent.upload(screen.getByLabelText("upload"), file);
16+
17+
//loading is now shown
18+
screen.getByText("loading");
19+
20+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
21+
22+
expect(global.xhrOpen.mock.calls[0][0]).toEqual("POST");
23+
expect(global.xhrOpen.mock.calls[0][1]).toEqual("github.gov");
24+
//check the type sent to the server
25+
expect(global.xhrSend.mock.calls[0][0].name).toEqual("hello.png");
26+
expect(global.xhrSend.mock.calls[0][0].type).toEqual("image/png");
27+
});
28+
29+
test("Renders done after upload is complete", async () => {
30+
global.xhrListener = jest.fn();
31+
render(<BasicTestUpload method="POST" url="github.gov" />);
32+
33+
//upload a file into the input
34+
const file = new File(["hello"], "hello.png", { type: "image/png" });
35+
userEvent.upload(screen.getByLabelText("upload"), file);
36+
//wait for the next tick
37+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
38+
39+
expect(global.xhrListener.mock.calls[1][0]).toEqual("load");
40+
expect(screen.queryByText("done")).toEqual(null);
41+
42+
//call the load method on the xhr mock
43+
act(() => global.xhrListener.mock.calls[1][1]());
44+
45+
expect(screen.getByText("done")).toBeTruthy();
46+
});
47+
48+
test("Renders upload progress", async () => {
49+
global.xhrListener = jest.fn();
50+
render(<BasicTestUpload method="POST" url="github.gov" />);
51+
52+
//upload a file into the input
53+
const file = new File(["hello"], "hello.png", { type: "image/png" });
54+
userEvent.upload(screen.getByLabelText("upload"), file);
55+
//wait for the next tick
56+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
57+
58+
expect(global.xhrListener.mock.calls[0][0]).toEqual("progress");
59+
expect(screen.queryByText("done")).toEqual(null);
60+
61+
//call the load method on the xhr mock
62+
act(() => global.xhrListener.mock.calls[0][1]({ loaded: 20, total: 100 }));
63+
64+
expect(screen.getByText("loading")).toBeTruthy();
65+
expect(screen.getByText("20% progress")).toBeTruthy();
66+
});
67+
68+
test("Renders error message if xhr fails", async () => {
69+
global.xhrListener = jest.fn();
70+
render(<BasicTestUpload method="POST" url="github.gov" />);
71+
72+
//upload a file into the input
73+
const file = new File(["hello"], "hello.png", { type: "image/png" });
74+
userEvent.upload(screen.getByLabelText("upload"), file);
75+
//wait for the next tick
76+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
77+
78+
expect(global.xhrListener.mock.calls[2][0]).toEqual("error");
79+
expect(screen.queryByText("bad error!")).toEqual(null);
80+
81+
//call the load method on the xhr mock
82+
act(() => global.xhrListener.mock.calls[2][1]("bad error!"));
83+
84+
expect(screen.getByText("bad error!")).toBeTruthy();
85+
});
86+
87+
test("Renders error message if xhr aborts", async () => {
88+
global.xhrListener = jest.fn();
89+
render(<BasicTestUpload method="POST" url="github.gov" />);
90+
91+
//upload a file into the input
92+
const file = new File(["hello"], "hello.png", { type: "image/png" });
93+
userEvent.upload(screen.getByLabelText("upload"), file);
94+
//wait for the next tick
95+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
96+
97+
expect(global.xhrListener.mock.calls[3][0]).toEqual("abort");
98+
expect(screen.queryByText("bad abort!")).toEqual(null);
99+
100+
//call the load method on the xhr mock
101+
act(() => global.xhrListener.mock.calls[3][1]("bad abort!"));
102+
103+
expect(screen.getByText("bad abort!")).toBeTruthy();
104+
});
105+
106+
test("Skips upload if options return null", async () => {
107+
let skipOptions = jest.fn();
108+
render(
109+
<BasicTestUpload
110+
method="POST"
111+
url="github.gov"
112+
skipOptionsCb={skipOptions}
113+
/>
114+
);
115+
116+
//upload a file into the input
117+
const file = new File(["hello"], "hello.png", { type: "image/png" });
118+
userEvent.upload(screen.getByLabelText("upload"), file);
119+
//wait for the next tick
120+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
121+
122+
expect(skipOptions.mock.calls.length).toEqual(1);
123+
});

tests/ResponseHeaders.test.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from "react";
2+
import { screen, render, act } from "@testing-library/react";
3+
import { BasicTestUpload } from "./TestComponents";
4+
import userEvent from "@testing-library/user-event";
5+
6+
test("Sets response headers into an object", async () => {
7+
global.xhrListener = jest.fn();
8+
render(<BasicTestUpload method="POST" url="github.gov" />);
9+
10+
//upload a file into the input
11+
const file = new File(["hello"], "hello.png", { type: "image/png" });
12+
userEvent.upload(screen.getByLabelText("upload"), file);
13+
//wait for the next tick
14+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
15+
16+
expect(global.xhrListener.mock.calls[1][0]).toEqual("load");
17+
expect(screen.queryByText("done")).toEqual(null);
18+
19+
//call the load method on the xhr mock
20+
act(() => global.xhrListener.mock.calls[1][1]());
21+
22+
expect(screen.getByText("The test response header")).toBeTruthy();
23+
});
24+
25+
test("Sets request headers from options object", async () => {
26+
global.xhrRequestHeader = jest.fn();
27+
render(
28+
<BasicTestUpload
29+
method="POST"
30+
url="github.gov"
31+
headers={{ yo: "awesome", another: "cool" }}
32+
/>
33+
);
34+
35+
//upload a file into the input
36+
const file = new File(["hello"], "hello.png", { type: "image/png" });
37+
userEvent.upload(screen.getByLabelText("upload"), file);
38+
//wait for the next tick
39+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
40+
41+
expect(global.xhrRequestHeader.mock.calls[0][0]).toEqual("yo");
42+
expect(global.xhrRequestHeader.mock.calls[0][1]).toEqual("awesome");
43+
expect(global.xhrRequestHeader.mock.calls[1][0]).toEqual("another");
44+
expect(global.xhrRequestHeader.mock.calls[1][1]).toEqual("cool");
45+
});

tests/TestComponents.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from "react";
2+
import { useEffect } from "react";
3+
import { useUpload } from "../src";
4+
5+
type Props = {
6+
method: string;
7+
url: string;
8+
headers?: any;
9+
skipOptionsCb?: () => any;
10+
};
11+
export const BasicTestUpload = ({
12+
skipOptionsCb,
13+
method,
14+
url,
15+
headers,
16+
}: Props) => {
17+
let [upload, { error, responseHeaders, progress, done, loading }] = useUpload(
18+
({ files }) => {
19+
if (skipOptionsCb) {
20+
skipOptionsCb();
21+
return undefined;
22+
}
23+
return {
24+
method,
25+
url,
26+
headers,
27+
body: files[0],
28+
};
29+
}
30+
);
31+
32+
return (
33+
<div>
34+
{responseHeaders ? <div>{responseHeaders.Test}</div> : null}
35+
{error ? <div>{error}</div> : null}
36+
{done ? <div>done</div> : null}
37+
{loading ? <div>loading</div> : null}
38+
{loading ? `${progress}% progress` : null}
39+
<input
40+
type="file"
41+
aria-label="upload"
42+
onChange={(e) => {
43+
if (e.target.files) {
44+
upload({ files: e.target.files });
45+
}
46+
}}
47+
/>
48+
</div>
49+
);
50+
};

tests/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare var xhrOpen: jest.Mock;
2+
declare var xhrSend: jest.Mock;
3+
declare var xhrListener: jest.Mock;
4+
declare var xhrRequestHeader: jest.Mock;

tests/setup.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
global.XMLHttpRequest = function () {
2+
let addEventListener = (name, callback) =>
3+
global.xhrListener?.(name, callback);
4+
let fake = () => true;
5+
return {
6+
open: global.xhrOpen ?? fake,
7+
send: global.xhrSend ?? fake,
8+
upload: {
9+
addEventListener,
10+
},
11+
addEventListener,
12+
setRequestHeader: global.xhrRequestHeader ?? fake,
13+
getAllResponseHeaders: () => "Test: The test response header",
14+
};
15+
};

0 commit comments

Comments
 (0)