diff --git a/src/components/dashboard/dashboard.component.test.tsx b/src/components/dashboard/dashboard.component.test.tsx index 3f97840..507eb9e 100644 --- a/src/components/dashboard/dashboard.component.test.tsx +++ b/src/components/dashboard/dashboard.component.test.tsx @@ -8,11 +8,65 @@ import userEvent from '@testing-library/user-event'; jest.mock('../../utils', () => ({ getAllPackagesFromLocalStorage: jest.fn(), + deletePackageFromLocalStorage: jest.fn(), })); const mockSetClinicalViews = jest.fn(); describe('DashboardComponent', () => { + const setupMocks = () => { + const mocks = { + setClinicalViews: jest.fn(), + consoleSpy: jest.spyOn(console, 'error').mockImplementation(() => {}), + localStorageGetItem: jest.fn(), + mockUrl: 'blob:mock-url', + mockAnchor: document.createElement('a'), + mockClick: jest.fn(), + originalCreateObjectURL: URL.createObjectURL, + originalRevokeObjectURL: URL.revokeObjectURL, + originalCreateElement: document.createElement.bind(document), + }; + + // Setup localStorage mock + Object.defineProperty(window, 'localStorage', { + value: { + getItem: mocks.localStorageGetItem, + setItem: jest.fn(), + removeItem: jest.fn(), + clear: jest.fn(), + }, + writable: true, + }); + + // Setup URL mocks + URL.createObjectURL = jest.fn(() => mocks.mockUrl); + URL.revokeObjectURL = jest.fn(); + + // Setup anchor element mock + Object.defineProperties(mocks.mockAnchor, { + click: { value: mocks.mockClick, writable: true }, + href: { value: '', writable: true }, + download: { value: '', writable: true }, + }); + + // Setup document.createElement mock + document.createElement = jest.fn((tag) => { + if (tag === 'a') { + return mocks.mockAnchor; + } + return mocks.originalCreateElement(tag); + }); + + return mocks; + }; + + const cleanupMocks = (mocks: ReturnType) => { + URL.createObjectURL = mocks.originalCreateObjectURL; + URL.revokeObjectURL = mocks.originalRevokeObjectURL; + document.createElement = mocks.originalCreateElement; + mocks.consoleSpy.mockRestore(); + }; + beforeEach(() => { (getAllPackagesFromLocalStorage as jest.Mock).mockReturnValue(mockContentPackages); }); @@ -73,4 +127,42 @@ describe('DashboardComponent', () => { await userEvent.type(searchInput, 'Clinical View 1'); expect(searchInput).toHaveValue('Clinical View 1'); }); + + it('handles downloading the schema', async () => { + const mocks = setupMocks(); + + mocks.localStorageGetItem.mockImplementation(() => mockContentPackages[0]); + + render( + key} + clinicalViews={mockContentPackages} + setClinicalViews={mocks.setClinicalViews} + />, + ); + + const downloadButton = await screen.findByRole('button', { + name: /downloadSchema/i, + }); + + await userEvent.click(downloadButton); + + expect(mocks.localStorageGetItem).toHaveBeenCalled(); + expect(URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob)); + expect(mocks.mockClick).toHaveBeenCalled(); + + mocks.localStorageGetItem.mockReset(); + mocks.mockClick.mockReset(); + mocks.consoleSpy.mockReset(); + + mocks.localStorageGetItem.mockReturnValue(null); + + await userEvent.click(downloadButton); + const blobCall = (URL.createObjectURL as jest.Mock).mock.calls[0][0]; + expect(blobCall).toBeInstanceOf(Blob); + expect(blobCall.type).toBe('application/json;charset=utf-8'); + + expect(mocks.mockAnchor.href).toBe('blob:mock-url'); + cleanupMocks(mocks); + }); }); diff --git a/src/components/dashboard/dashboard.component.tsx b/src/components/dashboard/dashboard.component.tsx index e230683..cd42a0f 100644 --- a/src/components/dashboard/dashboard.component.tsx +++ b/src/components/dashboard/dashboard.component.tsx @@ -35,7 +35,14 @@ interface ActionButtonsProps { onDownload: (key: string) => void; } -function ActionButtons({ responsiveSize, clinicalViewKey, t, onDelete, onDownload, onEdit }: ActionButtonsProps) { +export function ActionButtons({ + responsiveSize, + clinicalViewKey, + t, + onDelete, + onDownload, + onEdit, +}: ActionButtonsProps) { const defaultEnterDelayInMs = 300; const launchDeleteClinicalViewsPackageModal = () => { @@ -145,16 +152,34 @@ export function ContentPackagesList({ t, clinicalViews, setClinicalViews }: Cont const { paginated, goTo, results, currentPage } = usePagination(filteredViews, pageSize); const handleDownload = (key) => { - const schema = localStorage.getItem(`packageJSON_${key}`); - if (schema) { - const blob = new Blob([schema], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${key}.json`; - a.click(); - } else { - console.error('No schema found to download.'); + let url; + let anchor; + try { + const schema = localStorage.getItem(key); + if (!schema) { + console.error('No schema found to download.'); + return; + } + + const blob = new Blob([schema], { + type: 'application/json;charset=utf-8', + }); + url = URL.createObjectURL(blob); + + anchor = document.createElement('a'); + anchor.href = url; + anchor.download = `${key}.json`; + document.body.appendChild(anchor); + anchor.click(); + } catch (error) { + console.error('Error downloading schema:', error); + } finally { + if (url) { + URL.revokeObjectURL(url); + } + if (anchor && anchor.parentNode) { + anchor.parentNode.removeChild(anchor); + } } }; @@ -196,7 +221,8 @@ export function ContentPackagesList({ t, clinicalViews, setClinicalViews }: Cont const tableRows = results.map((contentPackage) => { const clinicalViewName = getNavGroupTitle(contentPackage); const dashboardTitles = getDashboardTitles(contentPackage); - const key = Object.keys(contentPackage)[0]; + + const key = Object.keys(contentPackage)[1]; return { id: contentPackage.key, @@ -272,8 +298,8 @@ export function ContentPackagesList({ t, clinicalViews, setClinicalViews }: Cont - {rows.map((row) => ( - + {rows.map((row, key) => ( + {row.cells.map((cell) => ( {cell.value} ))}