Skip to content

Commit

Permalink
feature: download file
Browse files Browse the repository at this point in the history
There is no easy way to facilitate a file download. You have to download
the file using a request, create a link and click that link
programmatically to send the file to the browser. I'd like to see that
functionality in this library to have a robust and consistent solution.

Added downloadFile function to provide file downloads to the browser.

Closes #61
  • Loading branch information
Gido Manders authored and gidomanders committed Sep 22, 2022
1 parent 70ec1a5 commit 1c0da15
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 1 deletion.
22 changes: 22 additions & 0 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,25 @@ export function remove<T>(url: string): Promise<T> {
.delete<T>(url)
.then((res) => res.data);
}

export async function downloadFile(url: string): Promise<void> {
const response = await getApi().get(url, {
responseType: 'blob'
});

const content = response.headers['content-disposition'];
if (!content) {
return;
}

const filename = content.split('filename=')[1].replace(/['"]+/g, '');
const link = document.createElement('a');
const href = URL.createObjectURL(response.data);

link.href = href;
link.setAttribute('download', filename);
link.click();

link.remove();
URL.revokeObjectURL(href);
}
80 changes: 79 additions & 1 deletion tests/request.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import mockAxios from 'jest-mock-axios';

import { get, patch, post, put, remove } from '../src/request';
import { downloadFile, get, patch, post, put, remove } from '../src/request';

// Note that we tests all the requests with the default middleware
describe('requests', () => {
Expand Down Expand Up @@ -284,4 +284,82 @@ describe('requests', () => {
expect(mockAxios.delete).toHaveBeenCalledWith('/api/pokemon/1');
});
});

describe('downloadFile', () => {
const createObjectURLSpy = jest.fn(() => 'object');
const revokeObjectURLSpy = jest.fn();
global.URL.createObjectURL = createObjectURLSpy;
global.URL.revokeObjectURL = revokeObjectURLSpy;
const clickSpy = jest.fn();
const setAttributeSpy = jest.fn();
const removeSpy = jest.fn();
const createElementSpy = jest
.spyOn(document, 'createElement')
// @ts-expect-error Test mock
.mockReturnValue({
click: clickSpy,
setAttribute: setAttributeSpy,
remove: removeSpy
});

afterEach(() => {
createElementSpy.mockReset();
createObjectURLSpy.mockReset();
revokeObjectURLSpy.mockReset();
clickSpy.mockReset();
setAttributeSpy.mockReset();
removeSpy.mockReset();
});

afterAll(() => {
createElementSpy.mockRestore();
createObjectURLSpy.mockRestore();
revokeObjectURLSpy.mockRestore();
});

test('with content-disposition', async () => {
expect.assertions(11);
const request = downloadFile('test');

mockAxios.mockResponseFor('test', {
data: 'testFile',
headers: {
'Content-Type': 'json',
'content-disposition': 'filename=test.test'
}
});

await expect(request).resolves.toBeUndefined();

expect(document.createElement).toHaveBeenCalledTimes(1);
expect(document.createElement).toHaveBeenCalledWith('a');
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
expect(createObjectURLSpy).toHaveBeenCalledWith('testFile');
expect(setAttributeSpy).toHaveBeenCalledTimes(1);
expect(setAttributeSpy).toHaveBeenCalledWith('download', 'test.test');
expect(clickSpy).toHaveBeenCalledTimes(1);
expect(removeSpy).toHaveBeenCalledTimes(1);
expect(revokeObjectURLSpy).toHaveBeenCalledTimes(1);
expect(revokeObjectURLSpy).toHaveBeenCalledWith('object');
});

test('without content-disposition', async () => {
expect.assertions(7);
const request = downloadFile('test');

mockAxios.mockResponseFor('test', {
data: 'error',
headers: { 'Content-Type': 'json' }
});

await expect(request).resolves.toBeUndefined();

expect(document.createElement).toHaveBeenCalledTimes(0);
expect(createObjectURLSpy).toHaveBeenCalledTimes(0);
expect(setAttributeSpy).toHaveBeenCalledTimes(0);
expect(clickSpy).toHaveBeenCalledTimes(0);
expect(removeSpy).toHaveBeenCalledTimes(0);
expect(revokeObjectURLSpy).toHaveBeenCalledTimes(0);
});
});
});

0 comments on commit 1c0da15

Please sign in to comment.