Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix double-extension on file download #1439

Merged
merged 10 commits into from
Dec 14, 2023
4 changes: 2 additions & 2 deletions src/shared/containers/ResourceActionsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const ResourceActionsContainer: React.FunctionComponent<{
deprecateMethod = nexus.File.deprecate;
}

const deprectatedResource = await deprecateMethod(
const deprecatedResource = await deprecateMethod(
orgLabel,
projectLabel,
encodeURIComponent(resourceId),
Expand All @@ -130,7 +130,7 @@ const ResourceActionsContainer: React.FunctionComponent<{
message: `Deprecated ${getResourceLabel(resource)}`,
});

const { _rev } = deprectatedResource;
const { _rev } = deprecatedResource;
goToResource(
orgLabel,
projectLabel,
Expand Down
142 changes: 142 additions & 0 deletions src/shared/utils/__tests__/download.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { download } from '../download';
import { fileExtensionFromResourceEncoding } from '../../../utils/contentTypes';

// Mock fileExtensionFromResourceEncoding
jest.mock('../../../utils/contentTypes', () => ({
fileExtensionFromResourceEncoding: jest.fn(),
}));

describe('download function', () => {
const mockCreateObjectURL = jest.fn();
const mockRevokeObjectURL = jest.fn();

beforeAll(() => {
globalThis.Blob = jest.fn(() => ({})) as any;

// Mock URL.createObjectURL and URL.revokeObjectURL
globalThis.URL.createObjectURL = mockCreateObjectURL;
globalThis.URL.revokeObjectURL = mockRevokeObjectURL;

(fileExtensionFromResourceEncoding as jest.Mock).mockImplementation(
(mediaType: string) => {
if (mediaType === 'application/json') {
return 'json';
}
return '';
}
);
});

beforeEach(() => {
mockCreateObjectURL.mockReset();
mockRevokeObjectURL.mockReset();
});

it('should handle filename with existing correct extension', () => {
const linkClickMock = jest.fn();
jest.spyOn(document, 'createElement').mockImplementation(
() =>
({
set href(url: string) {
mockCreateObjectURL(url);
},
click: linkClickMock,
download: '',
} as any)
);

download('example.json', 'application/json', 'test data');
expect(linkClickMock).toHaveBeenCalled();
expect(mockCreateObjectURL).toHaveBeenCalled();
});

it('should handle filename with duplicate extension', () => {
const linkClickMock = jest.fn();
jest.spyOn(document, 'createElement').mockImplementation(
() =>
({
set href(url: string) {
mockCreateObjectURL(url);
},
click: linkClickMock,
download: '',
} as any)
);

download('example.json.json', 'application/json', 'test data');
expect(linkClickMock).toHaveBeenCalled();
expect(mockCreateObjectURL).toHaveBeenCalled();
});

it('should append extension if missing', () => {
const linkClickMock = jest.fn();
jest.spyOn(document, 'createElement').mockImplementation(
() =>
({
set href(url: string) {
mockCreateObjectURL(url);
},
click: linkClickMock,
download: '',
} as any)
);

download('example', 'application/json', 'test data');
expect(linkClickMock).toHaveBeenCalled();
expect(mockCreateObjectURL).toHaveBeenCalled();
});

it('should handle if filename is another type than extension', () => {
const linkClickMock = jest.fn();
jest.spyOn(document, 'createElement').mockImplementation(
() =>
({
set href(url: string) {
mockCreateObjectURL(url);
},
click: linkClickMock,
download: '',
} as any)
);

download('example.png', 'application/json', 'test data');
expect(linkClickMock).toHaveBeenCalled();
expect(mockCreateObjectURL).toHaveBeenCalled();
});

it('should handle if filename is another type than extension', () => {
const linkClickMock = jest.fn();
jest.spyOn(document, 'createElement').mockImplementation(
() =>
({
set href(url: string) {
mockCreateObjectURL(url);
},
click: linkClickMock,
download: '',
} as any)
);

download('example.png', 'application/json', 'test data');
expect(linkClickMock).toHaveBeenCalled();
expect(mockCreateObjectURL).toHaveBeenCalled();
});

it('should download without a mediaType', () => {
const linkClickMock = jest.fn();
jest.spyOn(document, 'createElement').mockImplementation(
() =>
({
set href(url: string) {
mockCreateObjectURL(url);
},
click: linkClickMock,
download: '',
} as any)
);

download('example', undefined, 'test data');
expect(linkClickMock).toHaveBeenCalled();
expect(mockCreateObjectURL).toHaveBeenCalled();
});
});
21 changes: 16 additions & 5 deletions src/shared/utils/download.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { fileExtensionFromResourceEncoding } from '../../utils/contentTypes';

export const download = (filename: string, mediaType: string, data: any) => {
export const download = (
filename: string,
mediaType: string | undefined,
data: any
) => {
const blob = new Blob([data], { type: mediaType });
const extention = fileExtensionFromResourceEncoding(mediaType);
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, filename);
const extension = fileExtensionFromResourceEncoding(mediaType);

// Check if filename already ends with the correct extension
const hasCorrectExtension = filename.endsWith(`.${extension}`);

danburonline marked this conversation as resolved.
Show resolved Hide resolved
if ((window.navigator as any).msSaveBlob) {
(window.navigator as any).msSaveBlob(blob, filename);
} else {
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = extention ? `${filename}.${extention}` : filename;

// Only append extension if it's not already present or no mediaType is passed
link.download =
hasCorrectExtension || !mediaType ? filename : `${filename}.${extension}`;
link.click();
}
};
Expand Down
Loading