Commit 5c969487 authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Replace all file-downloading function with one which expects the type

parent 4ce317f5
function downloadFile( export function downloadFile(
content: string, content: string,
type: string, type: string,
filename: string, filename: string,
document: Document, /* istanbul ignore next - test seam */
document_ = document,
): void { ): void {
const blob = new Blob([content], { type }); const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document_.createElement('a');
link.setAttribute('href', url); link.setAttribute('href', url);
link.setAttribute('download', filename); link.setAttribute('download', filename);
link.style.visibility = 'hidden'; link.style.visibility = 'hidden';
document.body.appendChild(link); document_.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document_.body.removeChild(link);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
function buildTextFileDownloader(type: string) {
return (
text: string,
filename: string,
/* istanbul ignore next - test seam */
_document = document,
) => downloadFile(text, type, filename, _document);
}
export const downloadJSONFile = buildTextFileDownloader('application/json');
export const downloadTextFile = buildTextFileDownloader('text/plain');
export const downloadCSVFile = buildTextFileDownloader('text/csv');
export const downloadHTMLFile = buildTextFileDownloader('text/html');
import { import { downloadFile } from '../download-file';
downloadCSVFile,
downloadHTMLFile,
downloadJSONFile,
downloadTextFile,
} from '../download-file';
describe('download-file', () => { describe('download-file', () => {
let fakeLink; let fakeLink;
...@@ -47,39 +42,14 @@ describe('download-file', () => { ...@@ -47,39 +42,14 @@ describe('download-file', () => {
assert.equal('hidden', fakeLink.style.visibility); assert.equal('hidden', fakeLink.style.visibility);
} }
it('downloadJSONFile generates JSON file with provided data', () => { ['application/json', 'text/plain', 'text/csv', 'text/html'].forEach(type => {
const data = JSON.stringify({ foo: ['bar', 'baz'] }, null, 2); it('downloadTextFile generates text file with provided data', () => {
const filename = 'my-file.json'; const data = 'The content of the file';
const filename = 'my-file.txt';
downloadJSONFile(data, filename, fakeDocument); downloadFile(data, type, filename, fakeDocument);
assertDownloadHappened(filename, data, 'application/json'); assertDownloadHappened(filename, data, type);
}); });
it('downloadTextFile generates text file with provided data', () => {
const data = 'The content of the file';
const filename = 'my-file.txt';
downloadTextFile(data, filename, fakeDocument);
assertDownloadHappened(filename, data, 'text/plain');
});
it('downloadCSVFile generates csv file with provided data', () => {
const data = 'foo,bar,baz';
const filename = 'my-file.csv';
downloadCSVFile(data, filename, fakeDocument);
assertDownloadHappened(filename, data, 'text/csv');
});
it('downloadHTMLFile generates HTML file with provided data', () => {
const data = '<p>Hello</p>';
const filename = 'my-file.html';
downloadHTMLFile(data, filename, fakeDocument);
assertDownloadHappened(filename, data, 'text/html');
}); });
}); });
...@@ -8,12 +8,7 @@ import { ...@@ -8,12 +8,7 @@ import {
} from '@hypothesis/frontend-shared'; } from '@hypothesis/frontend-shared';
import { useCallback, useId, useMemo, useState } from 'preact/hooks'; import { useCallback, useId, useMemo, useState } from 'preact/hooks';
import { import { downloadFile } from '../../../shared/download-file';
downloadCSVFile,
downloadHTMLFile,
downloadJSONFile,
downloadTextFile,
} from '../../../shared/download-file';
import type { APIAnnotationData } from '../../../types/api'; import type { APIAnnotationData } from '../../../types/api';
import { annotationDisplayName } from '../../helpers/annotation-user'; import { annotationDisplayName } from '../../helpers/annotation-user';
import type { UserAnnotations } from '../../helpers/annotations-by-user'; import type { UserAnnotations } from '../../helpers/annotations-by-user';
...@@ -74,6 +69,16 @@ const exportFormats: ExportFormat[] = [ ...@@ -74,6 +69,16 @@ const exportFormats: ExportFormat[] = [
}, },
]; ];
function formatToMimeType(format: ExportFormat['value']): string {
const typeForFormat: Record<ExportFormat['value'], string> = {
json: 'application/json',
txt: 'text/plain',
csv: 'text/csv',
html: 'text/html',
};
return typeForFormat[format];
}
/** /**
* Render content for "export" tab panel: allow user to export annotations * Render content for "export" tab panel: allow user to export annotations
* with a specified filename. * with a specified filename.
...@@ -200,25 +205,9 @@ function ExportAnnotations({ ...@@ -200,25 +205,9 @@ function ExportAnnotations({
const format = exportFormat.value; const format = exportFormat.value;
const filename = `${customFilename ?? defaultFilename}.${format}`; const filename = `${customFilename ?? defaultFilename}.${format}`;
const exportData = buildExportContent(format); const exportData = buildExportContent(format);
const mimeType = formatToMimeType(format);
switch (format) { downloadFile(exportData, mimeType, filename);
case 'json': {
downloadJSONFile(exportData, filename);
break;
}
case 'txt': {
downloadTextFile(exportData, filename);
break;
}
case 'csv': {
downloadCSVFile(exportData, filename);
break;
}
case 'html': {
downloadHTMLFile(exportData, filename);
break;
}
}
} catch (e) { } catch (e) {
toastMessenger.error('Exporting annotations failed'); toastMessenger.error('Exporting annotations failed');
} }
......
...@@ -14,10 +14,7 @@ describe('ExportAnnotations', () => { ...@@ -14,10 +14,7 @@ describe('ExportAnnotations', () => {
let fakeStore; let fakeStore;
let fakeAnnotationsExporter; let fakeAnnotationsExporter;
let fakeToastMessenger; let fakeToastMessenger;
let fakeDownloadJSONFile; let fakeDownloadFile;
let fakeDownloadTextFile;
let fakeDownloadCSVFile;
let fakeDownloadHTMLFile;
let fakeSuggestedFilename; let fakeSuggestedFilename;
let fakeCopyPlainText; let fakeCopyPlainText;
let fakeCopyHTML; let fakeCopyHTML;
...@@ -48,10 +45,7 @@ describe('ExportAnnotations', () => { ...@@ -48,10 +45,7 @@ describe('ExportAnnotations', () => {
error: sinon.stub(), error: sinon.stub(),
success: sinon.stub(), success: sinon.stub(),
}; };
fakeDownloadJSONFile = sinon.stub(); fakeDownloadFile = sinon.stub();
fakeDownloadTextFile = sinon.stub();
fakeDownloadCSVFile = sinon.stub();
fakeDownloadHTMLFile = sinon.stub();
fakeStore = { fakeStore = {
defaultAuthority: sinon.stub().returns('example.com'), defaultAuthority: sinon.stub().returns('example.com'),
isFeatureEnabled: sinon.stub().returns(true), isFeatureEnabled: sinon.stub().returns(true),
...@@ -74,10 +68,7 @@ describe('ExportAnnotations', () => { ...@@ -74,10 +68,7 @@ describe('ExportAnnotations', () => {
$imports.$mock({ $imports.$mock({
'../../../shared/download-file': { '../../../shared/download-file': {
downloadJSONFile: fakeDownloadJSONFile, downloadFile: fakeDownloadFile,
downloadTextFile: fakeDownloadTextFile,
downloadCSVFile: fakeDownloadCSVFile,
downloadHTMLFile: fakeDownloadHTMLFile,
}, },
'../../helpers/export-annotations': { '../../helpers/export-annotations': {
suggestedFilename: fakeSuggestedFilename, suggestedFilename: fakeSuggestedFilename,
...@@ -408,21 +399,21 @@ describe('ExportAnnotations', () => { ...@@ -408,21 +399,21 @@ describe('ExportAnnotations', () => {
[ [
{ {
format: 'json', format: 'json',
getExpectedInvokedDownloader: () => fakeDownloadJSONFile, expectedMimeType: 'application/json',
}, },
{ {
format: 'txt', format: 'txt',
getExpectedInvokedDownloader: () => fakeDownloadTextFile, expectedMimeType: 'text/plain',
}, },
{ {
format: 'csv', format: 'csv',
getExpectedInvokedDownloader: () => fakeDownloadCSVFile, expectedMimeType: 'text/csv',
}, },
{ {
format: 'html', format: 'html',
getExpectedInvokedDownloader: () => fakeDownloadHTMLFile, expectedMimeType: 'text/html',
}, },
].forEach(({ format, getExpectedInvokedDownloader }) => { ].forEach(({ format, expectedMimeType }) => {
it('downloads a file using user-entered filename appended with proper extension', async () => { it('downloads a file using user-entered filename appended with proper extension', async () => {
const wrapper = createComponent(); const wrapper = createComponent();
const filenameInput = wrapper.find( const filenameInput = wrapper.find(
...@@ -436,11 +427,11 @@ describe('ExportAnnotations', () => { ...@@ -436,11 +427,11 @@ describe('ExportAnnotations', () => {
submitExportForm(wrapper); submitExportForm(wrapper);
const invokedDownloader = getExpectedInvokedDownloader(); assert.calledOnce(fakeDownloadFile);
assert.calledOnce(invokedDownloader);
assert.calledWith( assert.calledWith(
invokedDownloader, fakeDownloadFile,
sinon.match.any, sinon.match.any,
expectedMimeType,
`my-filename.${format}`, `my-filename.${format}`,
); );
}); });
...@@ -456,7 +447,7 @@ describe('ExportAnnotations', () => { ...@@ -456,7 +447,7 @@ describe('ExportAnnotations', () => {
submitExportForm(wrapper); submitExportForm(wrapper);
assert.notCalled(fakeDownloadJSONFile); assert.notCalled(fakeDownloadFile);
assert.calledOnce(fakeAnnotationsExporter.buildJSONExportContent); assert.calledOnce(fakeAnnotationsExporter.buildJSONExportContent);
assert.calledWith( assert.calledWith(
fakeToastMessenger.error, fakeToastMessenger.error,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment