diff --git a/react/jest.setup.ts b/react/jest.setup.ts index da882238..fb4c2a98 100644 --- a/react/jest.setup.ts +++ b/react/jest.setup.ts @@ -53,8 +53,9 @@ export function shouldIgnoreError(args: any[]): boolean { beforeAll(() => { // Establish mocking of APIs before all tests - server.listen({ onUnhandledRequest: 'error' }); - + server.listen({ + onUnhandledRequest: 'error', + }); const originalError = console.error; jest.spyOn(console, 'error').mockImplementation((...args: any[]) => { diff --git a/react/src/AppRouter.tsx b/react/src/AppRouter.tsx index dab56191..5a16e3c1 100644 --- a/react/src/AppRouter.tsx +++ b/react/src/AppRouter.tsx @@ -65,7 +65,10 @@ function AppRouter() { } /> - } /> + } + /> } /> ({ @@ -18,8 +19,8 @@ jest.mock('@hazmapper/components/FeatureFileTree', () => { describe('AssetsPanel', () => { const defaultProps = { featureCollection, - projectId: 1, - isPublic: false, + project: projectMock, + isPublicView: false, }; beforeEach(() => { diff --git a/react/src/components/AssetsPanel/AssetsPanel.tsx b/react/src/components/AssetsPanel/AssetsPanel.tsx index 64989237..84745e97 100644 --- a/react/src/components/AssetsPanel/AssetsPanel.tsx +++ b/react/src/components/AssetsPanel/AssetsPanel.tsx @@ -1,22 +1,26 @@ import React from 'react'; import styles from './AssetsPanel.module.css'; import FeatureFileTree from '@hazmapper/components/FeatureFileTree'; -import { FeatureCollection } from '@hazmapper/types'; +import { FeatureCollection, Project } from '@hazmapper/types'; import { Button } from '@tacc/core-components'; import { useFeatures } from '@hazmapper/hooks'; +const getFilename = (projectName: string) => { + // Convert to lowercase filename based on projectName + const sanitizedString = projectName.toLowerCase().replace(/[^a-z0-9]/g, '_'); + return `${sanitizedString}.json`; +}; + interface DownloadFeaturesButtonProps { - projectId: number; - isPublic: boolean; + project: Project; } const DownloadFeaturesButton: React.FC = ({ - projectId, - isPublic, + project, }) => { const { isLoading: isDownloading, refetch: triggerDownload } = useFeatures({ - projectId, - isPublic, + projectId: project.id, + isPublicView: project.public, assetTypes: [], // Empty array to get all features options: { enabled: false, // Only fetch when triggered by user clicking button @@ -31,7 +35,7 @@ const DownloadFeaturesButton: React.FC = ({ const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; - link.download = `hazmapper.json`; + link.download = getFilename(project.name); document.body.appendChild(link); link.click(); @@ -56,23 +60,23 @@ interface Props { featureCollection: FeatureCollection; /** - * Whether or not the map project is public. + * Whether or not the map project is a public view. */ - isPublic: boolean; + isPublicView: boolean; /** - * active project id + * active project */ - projectId: number; + project: Project; } /** * A panel component that displays info on feature assets */ const AssetsPanel: React.FC = ({ - isPublic, + isPublicView, featureCollection, - projectId, + project, }) => { return (
@@ -81,13 +85,13 @@ const AssetsPanel: React.FC = ({
- +
); diff --git a/react/src/components/DeleteMapModal/DeleteMapModal.tsx b/react/src/components/DeleteMapModal/DeleteMapModal.tsx index ee814844..4fb2db1e 100644 --- a/react/src/components/DeleteMapModal/DeleteMapModal.tsx +++ b/react/src/components/DeleteMapModal/DeleteMapModal.tsx @@ -20,13 +20,13 @@ const DeleteMapModal = ({ isLoading: isDeletingProject, isError, isSuccess, - } = useDeleteProject(project.id); + } = useDeleteProject(); const handleClose = () => { parentToggle(); }; const handleDeleteProject = () => { - deleteProject(undefined, {}); + deleteProject({ projectId: project.id }); }; return ( diff --git a/react/src/components/FeatureFileTree/FeatureFileTree.test.tsx b/react/src/components/FeatureFileTree/FeatureFileTree.test.tsx index acf52002..d1640b57 100644 --- a/react/src/components/FeatureFileTree/FeatureFileTree.test.tsx +++ b/react/src/components/FeatureFileTree/FeatureFileTree.test.tsx @@ -1,14 +1,10 @@ import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; +import { fireEvent, waitFor } from '@testing-library/react'; +import { http, HttpResponse } from 'msw'; import FeatureFileTree from './FeatureFileTree'; +import { server, renderInTest } from '@hazmapper/test/testUtil'; import { featureCollection } from '@hazmapper/__fixtures__/featuresFixture'; -import { useDeleteFeature } from '@hazmapper/hooks'; - -// Mock the hooks -jest.mock('@hazmapper/hooks', () => ({ - useDeleteFeature: jest.fn(), -})); +import { testDevConfiguration } from '@hazmapper/__fixtures__/appConfigurationFixture'; jest.mock('react-resize-detector', () => ({ useResizeDetector: () => ({ @@ -17,31 +13,20 @@ jest.mock('react-resize-detector', () => ({ }), })); -const renderWithRouter = (ui: React.ReactElement, { route = '/' } = {}) => { - return { - ...render({ui}), - }; -}; - describe('FeatureFileTree', () => { - const defaultProps = { + const defaultTreeProps = { featureCollection: featureCollection, - isPublic: false, + isPublicView: false, projectId: 1, }; beforeEach(() => { jest.clearAllMocks(); - - (useDeleteFeature as jest.Mock).mockImplementation(() => ({ - mutate: jest.fn(), - isLoading: false, - })); }); it('renders feature list correctly', () => { - const { getByText } = renderWithRouter( - + const { getByText } = renderInTest( + ); expect(getByText('foo')).toBeDefined(); @@ -49,32 +34,38 @@ describe('FeatureFileTree', () => { expect(getByText('image2.JPG')).toBeDefined(); }); - it('handles feature deletion for non-public projects', () => { - const deleteFeatureMock = jest.fn(); - (useDeleteFeature as jest.Mock).mockImplementation(() => ({ - mutate: deleteFeatureMock, - isLoading: false, - })); + it('handles feature deletion for non-public projects', async () => { + const featureId = 1; + let wasDeleted = false; + + server.use( + http.delete( + `${testDevConfiguration.geoapiUrl}/projects/${defaultTreeProps.projectId}/features/${featureId}/`, + () => { + wasDeleted = true; + return HttpResponse.json({}, { status: 200 }); + } + ) + ); - const { getByTestId } = renderWithRouter( - , - { route: '/?selectedFeature=1' } + const { getByTestId } = renderInTest( + , + `/?selectedFeature=${featureId}` ); // Find and click delete button (as featured is selected) const deleteButton = getByTestId('delete-feature-button'); fireEvent.click(deleteButton); - expect(deleteFeatureMock).toHaveBeenCalledWith({ - projectId: 1, - featureId: 1, + await waitFor(() => { + expect(wasDeleted).toBeTruthy(); }); }); it('does not show delete button for public projects', () => { - const { queryByTestId } = renderWithRouter( - , - { route: '/?selectedFeature=1' } + const { queryByTestId } = renderInTest( + , + '/?selectedFeature=1' ); // Verify delete button is not present @@ -83,8 +74,8 @@ describe('FeatureFileTree', () => { }); it('does not show delete button when no feature is selected', () => { - const { queryByTestId } = renderWithRouter( - + const { queryByTestId } = renderInTest( + ); // Verify delete button is not present diff --git a/react/src/components/FeatureFileTree/FeatureFileTree.tsx b/react/src/components/FeatureFileTree/FeatureFileTree.tsx index 1652597f..c41d17a0 100644 --- a/react/src/components/FeatureFileTree/FeatureFileTree.tsx +++ b/react/src/components/FeatureFileTree/FeatureFileTree.tsx @@ -31,7 +31,7 @@ interface FeatureFileTreeProps { /** * Whether or not the map project is public. */ - isPublic: boolean; + isPublicView: boolean; /** * active project id @@ -44,7 +44,7 @@ interface FeatureFileTreeProps { */ const FeatureFileTree: React.FC = ({ featureCollection, - isPublic, + isPublicView, projectId, }) => { const { mutate: deleteFeature, isLoading } = useDeleteFeature(); @@ -169,7 +169,7 @@ const FeatureFileTree: React.FC = ({ )} {featureNode.name} - {!isPublic && isSelected && ( + {!isPublicView && isSelected && (