diff --git a/src/shared/containers/ResourceActionsContainer.tsx b/src/shared/containers/ResourceActionsContainer.tsx index e4a0c34e2..a819eaef9 100644 --- a/src/shared/containers/ResourceActionsContainer.tsx +++ b/src/shared/containers/ResourceActionsContainer.tsx @@ -119,7 +119,7 @@ const ResourceActionsContainer: React.FunctionComponent<{ deprecateMethod = nexus.File.deprecate; } - const deprectatedResource = await deprecateMethod( + const deprecatedResource = await deprecateMethod( orgLabel, projectLabel, encodeURIComponent(resourceId), @@ -130,7 +130,7 @@ const ResourceActionsContainer: React.FunctionComponent<{ message: `Deprecated ${getResourceLabel(resource)}`, }); - const { _rev } = deprectatedResource; + const { _rev } = deprecatedResource; goToResource( orgLabel, projectLabel, diff --git a/src/shared/utils/__tests__/download.spec.ts b/src/shared/utils/__tests__/download.spec.ts new file mode 100644 index 000000000..1281b711d --- /dev/null +++ b/src/shared/utils/__tests__/download.spec.ts @@ -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(); + }); +}); diff --git a/src/shared/utils/download.ts b/src/shared/utils/download.ts index 2de99721f..08c7b7c58 100644 --- a/src/shared/utils/download.ts +++ b/src/shared/utils/download.ts @@ -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}`); + + 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(); } };