Skip to content

Commit 00e10f0

Browse files
Merge pull request #1297 from ral-facilities/feature/remove-primary-images-dialog-#1096
Feature/remove primary images dialog #1096
2 parents 0b94f19 + dac624b commit 00e10f0

7 files changed

+257
-33
lines changed

src/common/images/deleteImageDialog.component.test.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('delete Image dialogue', () => {
3131
beforeEach(() => {
3232
image = {
3333
id: '1',
34-
file_name: 'Image A',
34+
file_name: 'Image_A.png',
3535
entity_id: '2',
3636
title: 'a title',
3737
description: 'a description',
@@ -55,7 +55,7 @@ describe('delete Image dialogue', () => {
5555
createView();
5656
expect(screen.getByText('Delete Image')).toBeInTheDocument();
5757
expect(screen.getByTestId('delete-image-name')).toHaveTextContent(
58-
'a title'
58+
'Image_A.png'
5959
);
6060
});
6161

@@ -102,21 +102,6 @@ describe('delete Image dialogue', () => {
102102
expect(onClose).not.toHaveBeenCalled();
103103
});
104104

105-
it('displays warning message when session data is not loaded', async () => {
106-
props = {
107-
...props,
108-
image: undefined,
109-
};
110-
createView();
111-
const continueButton = screen.getByRole('button', { name: 'Continue' });
112-
await user.click(continueButton);
113-
const helperTexts = screen.getByText(
114-
'No data provided, Please refresh and try again'
115-
);
116-
expect(helperTexts).toBeInTheDocument();
117-
expect(onClose).not.toHaveBeenCalled();
118-
});
119-
120105
it('calls handleDeleteSession when continue button is clicked', async () => {
121106
createView();
122107
const continueButton = screen.getByRole('button', { name: 'Continue' });

src/common/images/deleteImageDialog.component.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import handleIMS_APIError from '../../handleIMS_APIError';
1818
export interface DeleteImageProps {
1919
open: boolean;
2020
onClose: () => void;
21-
image: APIImage | undefined;
21+
image: APIImage;
2222
}
2323

2424
const DeleteImageDialog = (props: DeleteImageProps) => {
@@ -58,7 +58,7 @@ const DeleteImageDialog = (props: DeleteImageProps) => {
5858
</DialogTitle>
5959
<DialogContent>
6060
Are you sure you want to permanently delete{' '}
61-
<strong data-testid="delete-image-name">{image?.title}</strong>?
61+
<strong data-testid="delete-image-name">{image.file_name}</strong>?
6262
</DialogContent>
6363
<DialogActions>
6464
<Button onClick={handleClose}>Cancel</Button>

src/common/images/primaryImage.component.test.tsx

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import userEvent, { UserEvent } from '@testing-library/user-event';
33
import { renderComponentWithRouterProvider } from '../../testUtils';
44
import PrimaryImage, { PrimaryImageProps } from './primaryImage.component'; // Adjust the import path as necessary
55

6+
vi.mock('../../handleIMS_APIError');
7+
68
let props: PrimaryImageProps;
79
let user: UserEvent;
810

@@ -27,17 +29,45 @@ describe('PrimaryImage Component', () => {
2729
expect(view.asFragment()).toMatchSnapshot();
2830
});
2931

30-
it('can open and close the set primary images dialog', async () => {
32+
it('can open and close the set primary image dialog', async () => {
33+
createView();
34+
await waitFor(() => {
35+
expect(
36+
screen.getByLabelText('primary images action menu')
37+
).toBeInTheDocument();
38+
});
39+
const actionButton = screen.getByLabelText('primary images action menu');
40+
user.click(actionButton);
41+
await waitFor(() => {
42+
expect(screen.getByText('Set Primary Image')).toBeInTheDocument();
43+
});
44+
const primaryImageButton = screen.getByText('Set Primary Image');
45+
await user.click(primaryImageButton);
46+
await waitFor(() => {
47+
expect(screen.getByRole('dialog')).toBeInTheDocument();
48+
});
49+
await waitFor(() =>
50+
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
51+
);
52+
expect((await screen.findAllByText('logo1.png')).length).toEqual(9);
53+
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
54+
await user.click(cancelButton);
55+
await waitFor(() => {
56+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
57+
});
58+
});
59+
60+
it('can open and close the remove primary image dialog', async () => {
3161
createView();
3262

3363
const actionButton = screen.getByLabelText('primary images action menu');
3464
user.click(actionButton);
3565

3666
await waitFor(() => {
37-
expect(screen.getByText('Set Primary Image')).toBeInTheDocument();
67+
expect(screen.getByText('Remove Primary Image')).toBeInTheDocument();
3868
});
3969

40-
const primaryImageButton = screen.getByText('Set Primary Image');
70+
const primaryImageButton = screen.getByText('Remove Primary Image');
4171
user.click(primaryImageButton);
4272

4373
await waitFor(() => {
@@ -48,12 +78,27 @@ describe('PrimaryImage Component', () => {
4878
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
4979
);
5080

51-
expect((await screen.findAllByText('logo1.png')).length).toEqual(9);
81+
const element = screen.getByTestId('remove-image-name');
82+
expect(element).toHaveTextContent('logo1.png');
5283

5384
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
5485
await user.click(cancelButton);
5586
await waitFor(() => {
5687
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
5788
});
5889
});
90+
91+
it('Removes the remove primary image button when there is no set primary image', async () => {
92+
props.entityId = '90';
93+
createView();
94+
95+
const actionButton = screen.getByLabelText('primary images action menu');
96+
user.click(actionButton);
97+
98+
await waitFor(() => {
99+
expect(
100+
screen.queryByText('Remove Primary Image')
101+
).not.toBeInTheDocument();
102+
});
103+
});
59104
});

src/common/images/primaryImage.component.tsx

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import DeleteIcon from '@mui/icons-material/Delete';
12
import EditIcon from '@mui/icons-material/Edit';
23
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
34
import {
@@ -14,14 +15,17 @@ import {
1415
Typography,
1516
} from '@mui/material';
1617
import React from 'react';
18+
import { useGetImages } from '../../api/images';
1719
import PrimaryImageDialog from './primaryImageDialog.component';
20+
import RemovePrimaryImageDialog from './removePrimaryImageDialog.component';
1821

1922
interface PrimaryOptionsMenuInterface {
20-
onChangePrimaryDialogOpen: (dialogOpen: boolean) => void;
23+
onChangePrimaryDialogOpen: (dialogOpen: false | 'set' | 'remove') => void;
24+
primaryImageExists: boolean;
2125
}
2226

2327
const PrimaryOptionsMenu = (props: PrimaryOptionsMenuInterface) => {
24-
const { onChangePrimaryDialogOpen } = props;
28+
const { onChangePrimaryDialogOpen, primaryImageExists } = props;
2529
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
2630
const open = Boolean(anchorEl);
2731

@@ -33,9 +37,14 @@ const PrimaryOptionsMenu = (props: PrimaryOptionsMenuInterface) => {
3337
setAnchorEl(null);
3438
};
3539

36-
const handleOpenPrimary = () => {
40+
const handleOpenSetPrimary = () => {
41+
onChangePrimaryDialogOpen('set');
42+
handleCloseMenu();
43+
};
44+
45+
const handleOpenRemovePrimary = () => {
46+
onChangePrimaryDialogOpen('remove');
3747
handleCloseMenu();
38-
onChangePrimaryDialogOpen(true);
3948
};
4049

4150
return (
@@ -52,12 +61,20 @@ const PrimaryOptionsMenu = (props: PrimaryOptionsMenuInterface) => {
5261
</span>
5362
</Tooltip>
5463
<Menu anchorEl={anchorEl} open={open} onClose={handleCloseMenu}>
55-
<MenuItem onClick={handleOpenPrimary}>
64+
<MenuItem onClick={handleOpenSetPrimary}>
5665
<ListItemIcon>
5766
<EditIcon />
5867
</ListItemIcon>
5968
<ListItemText>Set Primary Image</ListItemText>
6069
</MenuItem>
70+
{primaryImageExists && (
71+
<MenuItem onClick={handleOpenRemovePrimary}>
72+
<ListItemIcon>
73+
<DeleteIcon />
74+
</ListItemIcon>
75+
<ListItemText>Remove Primary Image</ListItemText>
76+
</MenuItem>
77+
)}
6178
</Menu>
6279
</Box>
6380
);
@@ -70,8 +87,14 @@ export interface PrimaryImageProps {
7087

7188
const PrimaryImage = (props: PrimaryImageProps) => {
7289
const { sx, entityId } = props;
73-
const [primaryDialogOpen, setPrimaryDialogOpen] =
74-
React.useState<boolean>(false);
90+
91+
const { data: imagesData } = useGetImages(entityId, true);
92+
93+
const primaryImageExists = !!imagesData && imagesData.length > 0;
94+
95+
const [primaryDialogOpen, setPrimaryDialogOpen] = React.useState<
96+
false | 'set' | 'remove'
97+
>(false);
7598
return (
7699
<Grid sx={{ height: '100%', width: '100%' }}>
77100
<Box
@@ -92,15 +115,27 @@ const PrimaryImage = (props: PrimaryImageProps) => {
92115
<Typography variant="h5">No Image</Typography>
93116
</Box>
94117
<Box sx={{ height: '20%' }}>
95-
<PrimaryOptionsMenu onChangePrimaryDialogOpen={setPrimaryDialogOpen} />
118+
<PrimaryOptionsMenu
119+
onChangePrimaryDialogOpen={setPrimaryDialogOpen}
120+
primaryImageExists={primaryImageExists}
121+
/>
96122
</Box>
97123
<PrimaryImageDialog
98-
open={primaryDialogOpen}
124+
open={primaryDialogOpen == 'set'}
99125
onClose={() => {
100126
setPrimaryDialogOpen(false);
101127
}}
102128
entityID={entityId}
103129
/>
130+
{primaryImageExists && (
131+
<RemovePrimaryImageDialog
132+
open={primaryDialogOpen == 'remove'}
133+
onClose={() => {
134+
setPrimaryDialogOpen(false);
135+
}}
136+
image={imagesData[0]}
137+
/>
138+
)}
104139
</Grid>
105140
);
106141
};

src/common/images/primaryImageDialog.component.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('Primary Image Dialog', () => {
4444
});
4545

4646
await waitFor(() =>
47-
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
47+
expect(screen.queryAllByRole('progressbar')).toHaveLength(0)
4848
);
4949

5050
expect((await screen.findAllByText('logo1.png')).length).toEqual(9);
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { RenderResult, screen, waitFor } from '@testing-library/react';
2+
import userEvent, { UserEvent } from '@testing-library/user-event';
3+
import { APIImageWithURL } from '../../api/api.types';
4+
import handleIMS_APIError from '../../handleIMS_APIError';
5+
import {
6+
CREATED_MODIFIED_TIME_VALUES,
7+
renderComponentWithRouterProvider,
8+
} from '../../testUtils';
9+
import RemovePrimaryImageDialog, {
10+
RemovePrimaryImageProps,
11+
} from './removePrimaryImageDialog.component';
12+
13+
vi.mock('../../handleIMS_APIError');
14+
15+
describe('Remove Primary Image dialogue', () => {
16+
let props: RemovePrimaryImageProps;
17+
let user: UserEvent;
18+
const onClose = vi.fn();
19+
let image: APIImageWithURL | undefined;
20+
const createView = (): RenderResult => {
21+
return renderComponentWithRouterProvider(
22+
<RemovePrimaryImageDialog {...props} />
23+
);
24+
};
25+
26+
beforeEach(() => {
27+
image = {
28+
id: '1',
29+
file_name: 'Image A',
30+
entity_id: '2',
31+
title: 'a title',
32+
description: 'a description',
33+
primary: false,
34+
thumbnail_base64: 'base64_thumbnail_test',
35+
...CREATED_MODIFIED_TIME_VALUES,
36+
view_url: 'view_url',
37+
download_url: 'download_url',
38+
};
39+
props = {
40+
open: true,
41+
onClose: onClose,
42+
image: image,
43+
};
44+
user = userEvent.setup(); // Assigning userEvent to 'user'
45+
});
46+
47+
afterEach(() => {
48+
vi.clearAllMocks();
49+
});
50+
51+
it('renders correctly', async () => {
52+
createView();
53+
expect(screen.getByText('Remove Primary Image')).toBeInTheDocument();
54+
expect(screen.getByTestId('remove-image-name')).toHaveTextContent(
55+
'Image A'
56+
);
57+
});
58+
59+
it('calls onClose when Close button is clicked', async () => {
60+
createView();
61+
const closeButton = screen.getByRole('button', { name: 'Cancel' });
62+
await user.click(closeButton);
63+
64+
await waitFor(() => {
65+
expect(onClose).toHaveBeenCalled();
66+
});
67+
});
68+
69+
it('calls handlePatchImage when continue button is clicked', async () => {
70+
createView();
71+
const continueButton = screen.getByRole('button', { name: 'Continue' });
72+
await user.click(continueButton);
73+
74+
await waitFor(() => {
75+
expect(onClose).toHaveBeenCalled();
76+
});
77+
});
78+
79+
it('displays error message if an unknown error occurs', async () => {
80+
if (image) image.file_name = 'Error_500.png';
81+
createView();
82+
const continueButton = screen.getByRole('button', { name: 'Continue' });
83+
await user.click(continueButton);
84+
85+
await waitFor(() => {
86+
expect(handleIMS_APIError).toHaveBeenCalled();
87+
});
88+
});
89+
});

0 commit comments

Comments
 (0)