Skip to content

Commit 19285dd

Browse files
committed
test: add more testing
1 parent f2460dc commit 19285dd

File tree

2 files changed

+196
-116
lines changed

2 files changed

+196
-116
lines changed

src/__tests__/index.spec.js

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
/* eslint-disable no-plusplus */
2+
/* eslint-disable jest/no-focused-tests */
13
/* eslint-disable no-prototype-builtins */
4+
import { fetch, Response } from 'node-fetch';
5+
import { ReadableStream } from 'web-streams-polyfill/ponyfill';
26
import { renderHook, act } from '@testing-library/react-hooks';
37
import useDownloader, { jsDownload } from '../index';
48

@@ -12,7 +16,79 @@ const expectedKeys = [
1216
'isInProgress',
1317
];
1418

15-
describe('useDownloader', () => {
19+
beforeAll(() => {
20+
global.window.fetch = fetch;
21+
global.Response = Response;
22+
global.ReadableStream = ReadableStream;
23+
});
24+
25+
describe('useDownloader successes', () => {
26+
beforeAll(() => {
27+
process.env.REACT_APP_DEBUG_MODE = 'true';
28+
window.URL = {
29+
createObjectURL: () => true,
30+
revokeObjectURL: () => true,
31+
};
32+
window.webkitURL = {
33+
createObjectURL: () => true,
34+
};
35+
36+
const pieces = [
37+
new Uint8Array([65, 98, 99, 32, 208]), // "Abc " and first byte of "й"
38+
new Uint8Array([185, 209, 139, 209, 141]), // Second byte of "й" and "ыэ"
39+
];
40+
41+
const fakeHeaders = {
42+
'content-encoding': '',
43+
'x-file-size': 123,
44+
'content-length': 456,
45+
};
46+
47+
global.window.fetch = jest.fn(() =>
48+
Promise.resolve({
49+
ok: true,
50+
headers: {
51+
...fakeHeaders,
52+
get: (header) => fakeHeaders[header],
53+
},
54+
body: {
55+
getReader() {
56+
let i = 0;
57+
58+
return {
59+
read() {
60+
return Promise.resolve(
61+
i < pieces.length
62+
? { value: pieces[i++], done: false }
63+
: { value: undefined, done: true }
64+
);
65+
},
66+
};
67+
},
68+
},
69+
blob: () => Promise.resolve({}),
70+
})
71+
);
72+
});
73+
74+
it('should run through', async () => {
75+
const { result, waitForNextUpdate } = renderHook(() => useDownloader());
76+
77+
expect(result.current.isInProgress).toBeFalsy();
78+
79+
act(() => {
80+
result.current.download('https://url.com', 'filename');
81+
});
82+
83+
expect(result.current.isInProgress).toBeTruthy();
84+
85+
await waitForNextUpdate();
86+
87+
expect(result.current.isInProgress).toBeFalsy();
88+
});
89+
});
90+
91+
describe('useDownloader failures', () => {
1692
it('should be defined', () => {
1793
const { result } = renderHook(() => useDownloader());
1894
expect(result).toBeDefined();
@@ -64,7 +140,7 @@ describe('useDownloader', () => {
64140

65141
const isInProgressRef = result.current.isInProgress;
66142

67-
expect(result.current.isInProgress).toBeTruthy();
143+
expect(isInProgressRef).toBeTruthy();
68144

69145
act(() => {
70146
result.current.download('https://url2.com', 'filename 2');
@@ -83,7 +159,10 @@ describe('useDownloader', () => {
83159
const { result, waitForNextUpdate } = renderHook(() => useDownloader());
84160

85161
global.window.fetch = jest.fn(() =>
86-
Promise.resolve({ ok: true, body: undefined })
162+
Promise.resolve({
163+
ok: true,
164+
body: undefined,
165+
})
87166
);
88167

89168
act(() => {

src/index.js

Lines changed: 114 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,99 @@
11
import { useCallback, useMemo, useRef, useState } from 'react';
22

3-
export const resolver = ({
4-
setSize,
5-
setControllerCallback,
6-
setPercentageCallback,
7-
setErrorCallback,
8-
}) => (response) => {
9-
if (!response.ok) {
10-
throw Error(`${response.status} ${response.type} ${response.statusText}`);
11-
}
3+
export const resolver =
4+
({
5+
setSize,
6+
setControllerCallback,
7+
setPercentageCallback,
8+
setErrorCallback,
9+
}) =>
10+
(response) => {
11+
if (!response.ok) {
12+
throw Error(`${response.status} ${response.type} ${response.statusText}`);
13+
}
1214

13-
if (!response.body) {
14-
throw Error('ReadableStream not yet supported in this browser.');
15-
}
15+
if (!response.body) {
16+
throw Error('ReadableStream not yet supported in this browser.');
17+
}
1618

17-
const contentEncoding = response.headers.get('content-encoding');
18-
const contentLength = response.headers.get(
19-
contentEncoding ? 'x-file-size' : 'content-length'
20-
);
19+
const contentEncoding = response.headers.get('content-encoding');
20+
const contentLength = response.headers.get(
21+
contentEncoding ? 'x-file-size' : 'content-length'
22+
);
2123

22-
const total = parseInt(contentLength || 0, 10);
24+
const total = parseInt(contentLength || 0, 10);
2325

24-
setSize(() => total);
26+
setSize(() => total);
2527

26-
let loaded = 0;
28+
let loaded = 0;
2729

28-
const stream = new ReadableStream({
29-
start(controller) {
30-
setControllerCallback(controller);
30+
const stream = new ReadableStream({
31+
start(controller) {
32+
setControllerCallback(controller);
3133

32-
const reader = response.body.getReader();
34+
const reader = response.body.getReader();
3335

34-
function read() {
35-
return reader
36-
.read()
37-
.then(({ done, value }) => {
38-
if (done) {
39-
return controller.close();
40-
}
36+
function read() {
37+
return reader
38+
.read()
39+
.then(({ done, value }) => {
40+
if (done) {
41+
return controller.close();
42+
}
4143

42-
loaded += value.byteLength;
44+
loaded += value.byteLength;
4345

44-
controller.enqueue(value);
46+
controller.enqueue(value);
4547

46-
setPercentageCallback({ loaded, total });
48+
setPercentageCallback({ loaded, total });
4749

48-
return read();
49-
})
50-
.catch((error) => {
51-
setErrorCallback(error);
52-
reader.cancel('Cancelled');
53-
return controller.error(error);
54-
});
55-
}
50+
return read();
51+
})
52+
.catch((error) => {
53+
setErrorCallback(error);
54+
reader.cancel('Cancelled');
55+
return controller.error(error);
56+
});
57+
}
5658

57-
return read();
58-
},
59-
});
59+
return read();
60+
},
61+
});
6062

61-
return new Response(stream);
62-
};
63+
return new Response(stream);
64+
};
6365

64-
export function jsDownload(data, filename, mime, bom) {
66+
export const jsDownload = (data, filename, mime, bom) => {
6567
const blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
6668
const blob = new Blob(blobData, {
6769
type: mime || 'application/octet-stream',
6870
});
69-
if (typeof window.navigator.msSaveBlob !== 'undefined') {
70-
window.navigator.msSaveBlob(blob, filename);
71-
} else {
72-
const blobURL =
73-
window.URL && window.URL.createObjectURL
74-
? window.URL.createObjectURL(blob)
75-
: window.webkitURL.createObjectURL(blob);
76-
const tempLink = document.createElement('a');
77-
tempLink.style.display = 'none';
78-
tempLink.href = blobURL;
79-
tempLink.setAttribute('download', filename);
80-
81-
if (typeof tempLink.download === 'undefined') {
82-
tempLink.setAttribute('target', '_blank');
83-
}
8471

85-
document.body.appendChild(tempLink);
86-
tempLink.click();
72+
if (typeof window.navigator.msSaveBlob !== 'undefined') {
73+
return window.navigator.msSaveBlob(blob, filename);
74+
}
8775

88-
setTimeout(() => {
89-
document.body.removeChild(tempLink);
90-
window.URL.revokeObjectURL(blobURL);
91-
}, 200);
76+
const blobURL =
77+
window.URL && window.URL.createObjectURL
78+
? window.URL.createObjectURL(blob)
79+
: window.webkitURL.createObjectURL(blob);
80+
const tempLink = document.createElement('a');
81+
tempLink.style.display = 'none';
82+
tempLink.href = blobURL;
83+
tempLink.setAttribute('download', filename);
84+
85+
if (typeof tempLink.download === 'undefined') {
86+
tempLink.setAttribute('target', '_blank');
9287
}
93-
}
88+
89+
document.body.appendChild(tempLink);
90+
tempLink.click();
91+
92+
return setTimeout(() => {
93+
document.body.removeChild(tempLink);
94+
window.URL.revokeObjectURL(blobURL);
95+
}, 200);
96+
};
9497

9598
export default function useDownloader() {
9699
const debugMode = process.env.REACT_APP_DEBUG_MODE;
@@ -143,55 +146,53 @@ export default function useDownloader() {
143146
}, [setControllerCallback]);
144147

145148
const handleDownload = useCallback(
146-
(downloadUrl, filename) => {
147-
async function startDownload() {
148-
clearAllStateCallback();
149-
setError(() => null);
150-
setIsInProgress(() => true);
151-
152-
const interval = setInterval(
153-
() => setElapsed((prevValue) => prevValue + 1),
154-
debugMode ? 1 : 1000
155-
);
156-
const resolverWithProgress = resolver({
157-
setSize,
158-
setControllerCallback,
159-
setPercentageCallback,
160-
setErrorCallback,
161-
});
162-
163-
return fetch(downloadUrl, {
164-
method: 'GET',
149+
async (downloadUrl, filename) => {
150+
if (isInProgress) return null;
151+
152+
clearAllStateCallback();
153+
setError(() => null);
154+
setIsInProgress(() => true);
155+
156+
const interval = setInterval(
157+
() => setElapsed((prevValue) => prevValue + 1),
158+
debugMode ? 1 : 1000
159+
);
160+
const resolverWithProgress = resolver({
161+
setSize,
162+
setControllerCallback,
163+
setPercentageCallback,
164+
setErrorCallback,
165+
});
166+
167+
return fetch(downloadUrl, {
168+
method: 'GET',
169+
})
170+
.then(resolverWithProgress)
171+
.then((data) => {
172+
return data.blob();
165173
})
166-
.then(resolverWithProgress)
167-
.then((data) => data.blob())
168-
.then((response) => jsDownload(response, filename))
169-
.then(() => {
170-
clearAllStateCallback();
171-
172-
return clearInterval(interval);
173-
})
174-
.catch((err) => {
175-
clearAllStateCallback();
176-
setError((prevValue) => {
177-
const { message } = err;
178-
179-
if (message !== 'Failed to fetch') {
180-
return {
181-
errorMessage: err.message,
182-
};
183-
}
174+
.then((response) => jsDownload(response, filename))
175+
.then(() => {
176+
clearAllStateCallback();
184177

185-
return prevValue;
186-
});
178+
return clearInterval(interval);
179+
})
180+
.catch((err) => {
181+
clearAllStateCallback();
182+
setError((prevValue) => {
183+
const { message } = err;
184+
185+
if (message !== 'Failed to fetch') {
186+
return {
187+
errorMessage: err.message,
188+
};
189+
}
187190

188-
return clearInterval(interval);
191+
return prevValue;
189192
});
190-
}
191193

192-
if (!isInProgress) {
193-
startDownload();
194-
}
194+
return clearInterval(interval);
195+
});
195196
},
196197
[
197198
isInProgress,

0 commit comments

Comments
 (0)