From 8cc6f06bab9f79553688db2f030294f9e2115552 Mon Sep 17 00:00:00 2001 From: Matteo Guarnaccia Date: Mon, 16 Oct 2023 09:01:41 +0100 Subject: [PATCH 1/7] logic for delete - #72 --- src/api/manufacturer.tsx | 29 ++++++ .../deleteManufacturerDialog.component.tsx | 97 +++++++++++++++++++ src/manufacturer/manufacturer.component.tsx | 19 ++++ src/mocks/handlers.ts | 20 ++++ 4 files changed, 165 insertions(+) create mode 100644 src/manufacturer/deleteManufacturerDialog.component.tsx diff --git a/src/api/manufacturer.tsx b/src/api/manufacturer.tsx index 954f23b18..bf8a82982 100644 --- a/src/api/manufacturer.tsx +++ b/src/api/manufacturer.tsx @@ -71,3 +71,32 @@ export const useAddManufacturer = (): UseMutationResult< } ); }; + +const deleteManufacturer = async ( + session: ViewManufacturerResponse +): Promise => { + let apiUrl: string; + apiUrl = ''; + const settingsResult = await settings; + if (settingsResult) { + apiUrl = settingsResult['apiUrl']; + } + return axios + .delete(`${apiUrl}/v1/manufacturers/${session.id}`, {}) + .then((response) => response.data); +}; + +export const useDeleteManufacturer = (): UseMutationResult< + void, + AxiosError, + ViewManufacturerResponse +> => { + return useMutation( + (session: ViewManufacturerResponse) => deleteManufacturer(session), + { + onError: (error) => { + console.log('Got error ' + error.message); + }, + } + ); +}; diff --git a/src/manufacturer/deleteManufacturerDialog.component.tsx b/src/manufacturer/deleteManufacturerDialog.component.tsx new file mode 100644 index 000000000..4ea4d8355 --- /dev/null +++ b/src/manufacturer/deleteManufacturerDialog.component.tsx @@ -0,0 +1,97 @@ +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormHelperText, +} from '@mui/material'; +import React from 'react'; +import { ErrorParsing, ViewManufacturerResponse } from '../app.types'; +import { AxiosError } from 'axios'; +import { useDeleteManufacturer } from '../api/manufacturer'; + +export interface DeleteManufacturerProps { + open: boolean; + onClose: () => void; + manufacturer: ViewManufacturerResponse | undefined; + refetchData: () => void; +} + +const DeleteManufacturerDialog = (props: DeleteManufacturerProps) => { + const { open, onClose, manufacturer, refetchData } = props; + + const [error, setError] = React.useState(false); + const [errorMessage, setErrorMessage] = React.useState( + undefined + ); + + const { mutateAsync: deleteManufacturer } = useDeleteManufacturer(); + + const handleClose = React.useCallback(() => { + onClose(); + setError(false); + setErrorMessage(undefined); + console.log('closing'); + }, [onClose]); + + const handleDeleteManufacturer = React.useCallback(() => { + if (manufacturer) { + deleteManufacturer(manufacturer) + .then((response) => { + refetchData(); + onClose(); + }) + .catch((error: AxiosError) => { + const response = error.response?.data as ErrorParsing; + if (response && error.response?.status === 409) { + setError(true); + setErrorMessage( + `${response.detail} Please delete the Catalogue Item first` + ); + return; + } + setError(true); + setErrorMessage('Please refresh and try again'); + }); + } else { + setError(true); + setErrorMessage('No data provided, Please refresh and try again'); + } + }, [manufacturer, deleteManufacturer, onClose, refetchData]); + + return ( + + Delete Manufacturer + + Are you sure you want to delete{' '} + + {manufacturer?.name} + + ? + + + + + + {error && ( + + + {errorMessage} + + + )} + + ); +}; + +export default DeleteManufacturerDialog; diff --git a/src/manufacturer/manufacturer.component.tsx b/src/manufacturer/manufacturer.component.tsx index d66056ade..f482f4363 100644 --- a/src/manufacturer/manufacturer.component.tsx +++ b/src/manufacturer/manufacturer.component.tsx @@ -17,6 +17,8 @@ import EditIcon from '@mui/icons-material/Edit'; import React from 'react'; import { useManufacturers } from '../api/manufacturer'; import AddManufacturerDialog from './manufacturerDialog.component'; +import { ViewManufacturerResponse } from '../app.types'; +import DeleteManufacturerDialog from './deleteManufacturerDialog.component'; function Manufacturer() { const [manufacturerDialogOpen, setManufacturerDialogOpen] = @@ -25,6 +27,13 @@ function Manufacturer() { const { data: ManufacturerData, refetch: manufacturerDataRefetch } = useManufacturers(); + const [deleteManufacturerDialog, setDeleteManufacturerDialog] = + React.useState(false); + + const [selectedManufacturer, setSelectedManufacturer] = React.useState< + ViewManufacturerResponse | undefined + >(undefined); + const [hoveredRow, setHoveredRow] = React.useState(null); const tableHeight = `calc(100vh)-(64px + 36px +50px)`; const theme = useTheme(); @@ -133,6 +142,10 @@ function Manufacturer() { { + setDeleteManufacturerDialog(true); + setSelectedManufacturer(item); + }} > @@ -190,6 +203,12 @@ function Manufacturer() { + setDeleteManufacturerDialog(false)} + manufacturer={selectedManufacturer} + refetchData={() => manufacturerDataRefetch()} + /> ); } diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 656250e1f..1dc910c2a 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -78,6 +78,26 @@ export const handlers = [ ); }), + rest.delete('/v1/manufacturers/:id', (req, res, ctx) => { + const { id } = req.params; + const validManufacturer = ManufacturerJSON.find((value) => value.id === id); + if (validManufacturer) { + if (id === '2') { + return res( + ctx.status(409), + ctx.json({ + detail: + 'The manufacturer is a part of a Catalogue Item, Please delete the Catalogue Item first', + }) + ); + } else { + return res(ctx.status(200), ctx.json('')); + } + } else { + return res(ctx.status(400), ctx.json('')); + } + }), + rest.delete('/v1/catalogue-categories/:id', (req, res, ctx) => { const { id } = req.params; const validCatalogueCategory = CatalogueCategoryJSON.find( From 1c939a4d94257679a64fd20fcc9da04a71a13b8c Mon Sep 17 00:00:00 2001 From: Matteo Guarnaccia Date: Mon, 16 Oct 2023 10:10:42 +0100 Subject: [PATCH 2/7] unit tests #72 --- .../AddmanufacturerDialog.component.test.tsx | 2 +- .../DeleteManufacturerDialog.test.tsx | 117 ++++++++++++++++++ ...sx => addManufacturerDialog.component.tsx} | 0 .../deleteManufacturerDialog.component.tsx | 1 - src/manufacturer/manufacturer.component.tsx | 2 +- 5 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 src/manufacturer/DeleteManufacturerDialog.test.tsx rename src/manufacturer/{manufacturerDialog.component.tsx => addManufacturerDialog.component.tsx} (100%) diff --git a/src/manufacturer/AddmanufacturerDialog.component.test.tsx b/src/manufacturer/AddmanufacturerDialog.component.test.tsx index be85ff656..e5ddded24 100644 --- a/src/manufacturer/AddmanufacturerDialog.component.test.tsx +++ b/src/manufacturer/AddmanufacturerDialog.component.test.tsx @@ -3,7 +3,7 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import AddManufacturerDialog, { AddManufacturerDialogProps, -} from './manufacturerDialog.component'; +} from './addManufacturerDialog.component'; import { renderComponentWithBrowserRouter } from '../setupTests'; describe('Add manufacturer dialog', () => { diff --git a/src/manufacturer/DeleteManufacturerDialog.test.tsx b/src/manufacturer/DeleteManufacturerDialog.test.tsx new file mode 100644 index 000000000..b812abe0f --- /dev/null +++ b/src/manufacturer/DeleteManufacturerDialog.test.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { RenderResult, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { renderComponentWithBrowserRouter } from '../setupTests'; +import { DeleteManufacturerProps } from './deleteManufacturerDialog.component'; +import DeleteManufacturerDialog from './deleteManufacturerDialog.component'; +import { ViewManufacturerResponse } from '../app.types'; + +describe('Delete Manufacturer Dialog', () => { + const onClose = jest.fn(); + const refetchData = jest.fn(); + let props: DeleteManufacturerProps; + let manufacturer: ViewManufacturerResponse; + let user; + const createView = (): RenderResult => { + return renderComponentWithBrowserRouter( + + ); + }; + beforeEach(() => { + manufacturer = { + name: 'test', + url: 'http://example.com', + address: { + building_number: '1', + street_name: 'Example Street', + town: 'Oxford', + county: 'Oxfordshire', + postCode: 'OX1 2AB', + }, + telephone: '056896598', + id: '1', + }; + props = { + open: true, + onClose: onClose, + refetchData: refetchData, + manufacturer: manufacturer, + }; + user = userEvent; + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders dialog correctly', async () => { + createView(); + expect(screen.getByText('Delete Manufacturer')).toBeInTheDocument(); + expect(screen.getByTestId('delete-manufacturer-name')).toHaveTextContent( + 'test' + ); + }); + + it('calls onClose when Close clicked', async () => { + createView(); + const closeButton = screen.getByRole('button', { name: 'Cancel' }); + await user.click(closeButton); + + await waitFor(() => { + expect(onClose).toHaveBeenCalled(); + }); + }); + + it('displays warning message when data not loaded', async () => { + props = { + ...props, + manufacturer: undefined, + }; + createView(); + const continueButton = screen.getByRole('button', { name: 'Continue' }); + await user.click(continueButton); + const helperTexts = screen.getByText( + 'No data provided, Please refresh and try again' + ); + expect(helperTexts).toBeInTheDocument(); + expect(onClose).not.toHaveBeenCalled(); + }); + + it('calls handleDelete when Continue clicked', async () => { + createView(); + const continueButton = screen.getByRole('button', { name: 'Continue' }); + user.click(continueButton); + + await waitFor(() => { + expect(onClose).toHaveBeenCalled(); + }); + expect(refetchData).toHaveBeenCalled(); + }); + + it('displays error message when user tries to delete a manufacturer that is a part of a catalogue item', async () => { + manufacturer.id = '2'; + createView(); + const continueButton = screen.getByRole('button', { name: 'Continue' }); + user.click(continueButton); + + await waitFor(() => { + expect( + screen.getByText( + 'The manufacturer is a part of a Catalogue Item, Please delete the Catalogue Item first Please delete the Catalogue Item first' + ) + ).toBeInTheDocument(); + }); + }); + + it('displays error message if an unknown error occurs', async () => { + manufacturer.id = '100'; + createView(); + const continueButton = screen.getByRole('button', { name: 'Continue' }); + user.click(continueButton); + + await waitFor(() => { + expect( + screen.getByText('Please refresh and try again') + ).toBeInTheDocument(); + }); + }); +}); diff --git a/src/manufacturer/manufacturerDialog.component.tsx b/src/manufacturer/addManufacturerDialog.component.tsx similarity index 100% rename from src/manufacturer/manufacturerDialog.component.tsx rename to src/manufacturer/addManufacturerDialog.component.tsx diff --git a/src/manufacturer/deleteManufacturerDialog.component.tsx b/src/manufacturer/deleteManufacturerDialog.component.tsx index 4ea4d8355..d697ff78e 100644 --- a/src/manufacturer/deleteManufacturerDialog.component.tsx +++ b/src/manufacturer/deleteManufacturerDialog.component.tsx @@ -33,7 +33,6 @@ const DeleteManufacturerDialog = (props: DeleteManufacturerProps) => { onClose(); setError(false); setErrorMessage(undefined); - console.log('closing'); }, [onClose]); const handleDeleteManufacturer = React.useCallback(() => { diff --git a/src/manufacturer/manufacturer.component.tsx b/src/manufacturer/manufacturer.component.tsx index f482f4363..974f3d8ae 100644 --- a/src/manufacturer/manufacturer.component.tsx +++ b/src/manufacturer/manufacturer.component.tsx @@ -16,7 +16,7 @@ import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; import React from 'react'; import { useManufacturers } from '../api/manufacturer'; -import AddManufacturerDialog from './manufacturerDialog.component'; +import AddManufacturerDialog from './addManufacturerDialog.component'; import { ViewManufacturerResponse } from '../app.types'; import DeleteManufacturerDialog from './deleteManufacturerDialog.component'; From 04e35625cedd769f704f8f5cb03dd550e1c95ef7 Mon Sep 17 00:00:00 2001 From: Matteo Guarnaccia Date: Mon, 16 Oct 2023 10:42:14 +0100 Subject: [PATCH 3/7] e2e tests #72 --- cypress/e2e/manufacturer/manufacturer.cy.tsx | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cypress/e2e/manufacturer/manufacturer.cy.tsx b/cypress/e2e/manufacturer/manufacturer.cy.tsx index a888b8fea..2f6809931 100644 --- a/cypress/e2e/manufacturer/manufacturer.cy.tsx +++ b/cypress/e2e/manufacturer/manufacturer.cy.tsx @@ -91,6 +91,7 @@ describe('Manufacturer', () => { cy.contains('Please enter a post code or zip code.'); }); }); + it('displays error message when duplicate name entered', async () => { cy.findByTestId('Add Manufacturer').click(); cy.findByLabelText('Name *').type('Manufacturer A'); @@ -105,6 +106,7 @@ describe('Manufacturer', () => { cy.contains('A manufacturer with the same name already exists.'); }); }); + it('invalid url displays correct error message', async () => { cy.findByTestId('Add Manufacturer').click(); @@ -117,4 +119,35 @@ describe('Manufacturer', () => { cy.contains('Please enter a valid url.'); }); }); + + it('delete a manufacturer', () => { + cy.findAllByTestId('DeleteIcon').first().click(); + + cy.startSnoopingBrowserMockedRequest(); + + cy.findByRole('button', { name: 'Continue' }).click(); + + cy.findBrowserMockedRequests({ + method: 'DELETE', + url: '/v1/manufacturers/:id', + }).should((patchRequests) => { + expect(patchRequests.length).equal(1); + const request = patchRequests[0]; + expect(request.url.toString()).to.contain('1'); + }); + }); + + it('shows error when trying to delete manufacturer that is part of Catalogue Item', async () => { + cy.findAllByTestId('DeleteIcon').eq(1).click(); + + cy.findByRole('button', { name: 'Continue' }).click(); + + cy.findByRole('dialog') + .should('be.visible') + .within(() => { + cy.contains( + 'The manufacturer is a part of a Catalogue Item, Please delete the Catalogue Item first Please delete the Catalogue Item first' + ); + }); + }); }); From 1f40e5fe40b8eb001533c571ef6fbba18bb62c64 Mon Sep 17 00:00:00 2001 From: Matteo Guarnaccia Date: Thu, 19 Oct 2023 09:39:32 +0100 Subject: [PATCH 4/7] added unit tests for API --- src/api/manufacturer.test.tsx | 156 ++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/api/manufacturer.test.tsx diff --git a/src/api/manufacturer.test.tsx b/src/api/manufacturer.test.tsx new file mode 100644 index 000000000..fae475666 --- /dev/null +++ b/src/api/manufacturer.test.tsx @@ -0,0 +1,156 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { AddManufacturer, ViewManufacturerResponse } from '../app.types'; +import { hooksWrapperWithProviders } from '../setupTests'; +import { + useAddManufacturer, + useDeleteManufacturer, + useManufacturers, +} from './manufacturer'; + +describe('manufacturer api functions', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('useAddManufacturer', () => { + let mockDataAdd: AddManufacturer; + beforeEach(() => { + mockDataAdd = { + name: 'Manufacturer D', + url: 'http://test.co.uk', + address: { + building_number: '1', + street_name: 'Example', + town: 'Oxford', + county: 'Oxfordshire', + postCode: 'OX1 2AB', + }, + telephone: '07349612203', + }; + }); + + it('posts a request to add manufacturer and returns successful response', async () => { + const { result } = renderHook(() => useAddManufacturer(), { + wrapper: hooksWrapperWithProviders(), + }); + expect(result.current.isIdle).toBe(true); + result.current.mutate(mockDataAdd); + await waitFor(() => { + expect(result.current.isSuccess).toBeTruthy(); + }); + expect(result.current.data).toEqual({ + name: 'Manufacturer D', + code: 'manufacturer-d', + url: 'http://test.co.uk', + address: { + building_number: '1', + street_name: 'Example Street', + town: 'Oxford', + county: 'Oxfordshire', + postCode: 'OX1 2AB', + }, + telephone: '07349612203', + id: '4', + }); + }); + + it.todo( + 'sends axios request to fetch records and throws an appropiate error on failure' + ); + }); + + describe('useDeleteManufacturer', () => { + let mockDataView: ViewManufacturerResponse; + beforeEach(() => { + mockDataView = { + name: 'Manufacturer A', + url: 'http://example.com', + address: { + building_number: '1', + street_name: 'Example', + town: 'Oxford', + county: 'Oxfordshire', + postCode: 'OX1 2AB', + }, + telephone: '07334893348', + id: '1', + }; + }); + it('posts a request to delete a manufacturer and return a successful response', async () => { + const { result } = renderHook(() => useDeleteManufacturer(), { + wrapper: hooksWrapperWithProviders(), + }); + expect(result.current.isIdle).toBe(true); + result.current.mutate(mockDataView); + await waitFor(() => { + expect(result.current.isSuccess).toBeTruthy(); + }); + expect(result.current.data).toEqual(''); + }); + + it.todo( + 'sends axios request to fetch records and throws an appropriate error on fetch' + ); + }); + + describe('useManufacturer', () => { + it('sends request to fetch manufacturer data and returns successful response', async () => { + const { result } = renderHook(() => useManufacturers(), { + wrapper: hooksWrapperWithProviders(), + }); + + await waitFor(() => { + expect(result.current.isSuccess).toBeTruthy(); + }); + + expect(result.current.data).toEqual([ + { + id: '1', + name: 'Manufacturer A', + code: 'manufacturer-a', + url: 'http://example.com', + address: { + building_number: '1', + street_name: 'Example Street', + town: 'Oxford', + county: 'Oxfordshire', + postCode: 'OX1 2AB', + }, + telephone: '07334893348', + }, + { + id: '2', + name: 'Manufacturer B', + code: 'manufacturer-b', + url: 'http://test.com', + address: { + building_number: '2', + street_name: 'Example Street', + town: 'Oxford', + county: 'Oxfordshire', + postCode: 'OX1 2AB', + }, + telephone: '07294958549', + }, + { + id: '3', + name: 'Manufacturer C', + code: 'manufacturer-c', + url: 'http://test.co.uk', + address: { + building_number: '3', + street_name: 'Example Street', + town: 'Oxford', + county: 'Oxfordshire', + postCode: 'OX1 2AB', + }, + telephone: '07934303412', + }, + ]); + }); + + it.todo( + 'sends axios request to fetch records and throws an appropriate error on failure' + ); + }); +}); From 8d9b3ab7276a4fc0a2e07790bf244a756fc5a774 Mon Sep 17 00:00:00 2001 From: Matteo Guarnaccia Date: Tue, 24 Oct 2023 14:02:12 +0100 Subject: [PATCH 5/7] fix CI build #72 --- src/manufacturer/DeleteManufacturerDialog.test.tsx | 3 --- src/manufacturer/deleteManufacturerDialog.component.tsx | 6 ++---- src/manufacturer/manufacturer.component.tsx | 6 +++--- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/manufacturer/DeleteManufacturerDialog.test.tsx b/src/manufacturer/DeleteManufacturerDialog.test.tsx index b812abe0f..57c5037ab 100644 --- a/src/manufacturer/DeleteManufacturerDialog.test.tsx +++ b/src/manufacturer/DeleteManufacturerDialog.test.tsx @@ -8,7 +8,6 @@ import { ViewManufacturerResponse } from '../app.types'; describe('Delete Manufacturer Dialog', () => { const onClose = jest.fn(); - const refetchData = jest.fn(); let props: DeleteManufacturerProps; let manufacturer: ViewManufacturerResponse; let user; @@ -34,7 +33,6 @@ describe('Delete Manufacturer Dialog', () => { props = { open: true, onClose: onClose, - refetchData: refetchData, manufacturer: manufacturer, }; user = userEvent; @@ -84,7 +82,6 @@ describe('Delete Manufacturer Dialog', () => { await waitFor(() => { expect(onClose).toHaveBeenCalled(); }); - expect(refetchData).toHaveBeenCalled(); }); it('displays error message when user tries to delete a manufacturer that is a part of a catalogue item', async () => { diff --git a/src/manufacturer/deleteManufacturerDialog.component.tsx b/src/manufacturer/deleteManufacturerDialog.component.tsx index d697ff78e..131c6cabd 100644 --- a/src/manufacturer/deleteManufacturerDialog.component.tsx +++ b/src/manufacturer/deleteManufacturerDialog.component.tsx @@ -16,11 +16,10 @@ export interface DeleteManufacturerProps { open: boolean; onClose: () => void; manufacturer: ViewManufacturerResponse | undefined; - refetchData: () => void; } const DeleteManufacturerDialog = (props: DeleteManufacturerProps) => { - const { open, onClose, manufacturer, refetchData } = props; + const { open, onClose, manufacturer } = props; const [error, setError] = React.useState(false); const [errorMessage, setErrorMessage] = React.useState( @@ -39,7 +38,6 @@ const DeleteManufacturerDialog = (props: DeleteManufacturerProps) => { if (manufacturer) { deleteManufacturer(manufacturer) .then((response) => { - refetchData(); onClose(); }) .catch((error: AxiosError) => { @@ -58,7 +56,7 @@ const DeleteManufacturerDialog = (props: DeleteManufacturerProps) => { setError(true); setErrorMessage('No data provided, Please refresh and try again'); } - }, [manufacturer, deleteManufacturer, onClose, refetchData]); + }, [manufacturer, deleteManufacturer, onClose]); return ( diff --git a/src/manufacturer/manufacturer.component.tsx b/src/manufacturer/manufacturer.component.tsx index 3c8f5eed9..a5023c147 100644 --- a/src/manufacturer/manufacturer.component.tsx +++ b/src/manufacturer/manufacturer.component.tsx @@ -19,6 +19,7 @@ import { useManufacturers } from '../api/manufacturer'; import { ViewManufacturerResponse } from '../app.types'; import DeleteManufacturerDialog from './deleteManufacturerDialog.component'; import { ManufacturerDetail } from '../app.types'; +import AddManufacturerDialog from './addManufacturerDialog.component'; function Manufacturer() { const [addManufacturer, setAddManufacturer] = @@ -217,12 +218,11 @@ function Manufacturer() { - {/* setDeleteManufacturerDialog(false)} manufacturer={selectedManufacturer} - refetchData={() => manufacturerDataRefetch()} - /> */} + /> ); } From 1ab4fe3c512d86fc71cdb2f36e752e31813ef5b2 Mon Sep 17 00:00:00 2001 From: Matteo Guarnaccia Date: Wed, 8 Nov 2023 13:23:14 +0000 Subject: [PATCH 6/7] requested changes #72 --- cypress/e2e/manufacturer/manufacturer.cy.tsx | 19 ++++++++------- src/api/manufacturer.test.tsx | 16 ++++++------- src/api/manufacturer.tsx | 23 +++++++++---------- .../DeleteManufacturerDialog.test.tsx | 8 +++---- .../deleteManufacturerDialog.component.tsx | 6 ++--- .../manufacturer.component.test.tsx | 20 ++++++++++++++++ src/manufacturer/manufacturer.component.tsx | 8 +++---- src/mocks/handlers.ts | 3 +-- 8 files changed, 62 insertions(+), 41 deletions(-) diff --git a/cypress/e2e/manufacturer/manufacturer.cy.tsx b/cypress/e2e/manufacturer/manufacturer.cy.tsx index ca698f8bb..317284ebf 100644 --- a/cypress/e2e/manufacturer/manufacturer.cy.tsx +++ b/cypress/e2e/manufacturer/manufacturer.cy.tsx @@ -2,6 +2,9 @@ describe('Manufacturer', () => { beforeEach(() => { cy.visit('/inventory-management-system/manufacturer'); }); + afterEach(() => { + cy.clearMocks(); + }); it('should render in table headers', () => { cy.visit('/inventory-management-system/manufacturer'); @@ -45,7 +48,7 @@ describe('Manufacturer', () => { cy.url().should('include', 'http://example.com'); }); - it('adds a manufacturer with all fields', async () => { + it('adds a manufacturer with all fields', () => { cy.findByRole('button', { name: 'Add Manufacturer' }).click(); cy.findByLabelText('Name *').type('Manufacturer D'); cy.findByLabelText('URL').type('http://test.co.uk'); @@ -67,12 +70,12 @@ describe('Manufacturer', () => { expect(patchRequests.length).equal(1); const request = patchRequests[0]; expect(JSON.stringify(request.body)).equal( - '{"name":"Manufacturer D","url":"http://test.co.uk", "address": {building_number: "1", "street_name": "Example Street", "town": "Oxford", "county": "Oxfordshire", "postcode": "OX1 2AB",}, "telephone": "07349612203"}' + '{"name":"Manufacturer D","url":"http://test.co.uk","address":{"building_number":"1","street_name":"Example Street","town":"Oxford","county":"Oxfordshire","postcode":"OX1 2AB"},"telephone":"07349612203"}' ); }); }); - it('adds a manufacturer with only mandatory fields', async () => { + it('adds a manufacturer with only mandatory fields', () => { cy.findByRole('button', { name: 'Add Manufacturer' }).click(); cy.findByLabelText('Name *').type('Manufacturer D'); cy.findByLabelText('Building number *').type('1'); @@ -94,7 +97,7 @@ describe('Manufacturer', () => { ); }); - it('render error messages if fields are not filled', async () => { + it('render error messages if fields are not filled', () => { cy.findByTestId('Add Manufacturer').click(); cy.findByRole('button', { name: 'Save' }).click(); cy.findByRole('dialog') @@ -118,7 +121,7 @@ describe('Manufacturer', () => { cy.contains('Please enter a post code or zip code.'); }); }); - it('displays error message when duplicate name entered', async () => { + it('displays error message when duplicate name entered', () => { cy.findByTestId('Add Manufacturer').click(); cy.findByLabelText('Name *').type('Manufacturer A'); cy.findByLabelText('Building number *').type('1'); @@ -132,7 +135,7 @@ describe('Manufacturer', () => { cy.contains('A manufacturer with the same name already exists.'); }); }); - it('invalid url displays correct error message', async () => { + it('invalid url displays correct error message', () => { cy.findByTestId('Add Manufacturer').click(); cy.findByLabelText('URL').type('test.co.uk'); @@ -163,7 +166,7 @@ describe('Manufacturer', () => { }); }); - it('shows error when trying to delete manufacturer that is part of Catalogue Item', async () => { + it('shows error when trying to delete manufacturer that is part of Catalogue Item', () => { cy.findAllByTestId('DeleteIcon').eq(1).click(); cy.findByRole('button', { name: 'Continue' }).click(); @@ -172,7 +175,7 @@ describe('Manufacturer', () => { .should('be.visible') .within(() => { cy.contains( - 'The manufacturer is a part of a Catalogue Item, Please delete the Catalogue Item first Please delete the Catalogue Item first' + 'The specified manufacturer is a part of a Catalogue Item. Please delete the Catalogue Item first.' ); }); }); diff --git a/src/api/manufacturer.test.tsx b/src/api/manufacturer.test.tsx index fae475666..644760adb 100644 --- a/src/api/manufacturer.test.tsx +++ b/src/api/manufacturer.test.tsx @@ -1,5 +1,5 @@ import { renderHook, waitFor } from '@testing-library/react'; -import { AddManufacturer, ViewManufacturerResponse } from '../app.types'; +import { AddManufacturer, Manufacturer } from '../app.types'; import { hooksWrapperWithProviders } from '../setupTests'; import { useAddManufacturer, @@ -23,7 +23,7 @@ describe('manufacturer api functions', () => { street_name: 'Example', town: 'Oxford', county: 'Oxfordshire', - postCode: 'OX1 2AB', + postcode: 'OX1 2AB', }, telephone: '07349612203', }; @@ -47,7 +47,7 @@ describe('manufacturer api functions', () => { street_name: 'Example Street', town: 'Oxford', county: 'Oxfordshire', - postCode: 'OX1 2AB', + postcode: 'OX1 2AB', }, telephone: '07349612203', id: '4', @@ -60,7 +60,7 @@ describe('manufacturer api functions', () => { }); describe('useDeleteManufacturer', () => { - let mockDataView: ViewManufacturerResponse; + let mockDataView: Manufacturer; beforeEach(() => { mockDataView = { name: 'Manufacturer A', @@ -70,7 +70,7 @@ describe('manufacturer api functions', () => { street_name: 'Example', town: 'Oxford', county: 'Oxfordshire', - postCode: 'OX1 2AB', + postcode: 'OX1 2AB', }, telephone: '07334893348', id: '1', @@ -114,7 +114,7 @@ describe('manufacturer api functions', () => { street_name: 'Example Street', town: 'Oxford', county: 'Oxfordshire', - postCode: 'OX1 2AB', + postcode: 'OX1 2AB', }, telephone: '07334893348', }, @@ -128,7 +128,7 @@ describe('manufacturer api functions', () => { street_name: 'Example Street', town: 'Oxford', county: 'Oxfordshire', - postCode: 'OX1 2AB', + postcode: 'OX1 2AB', }, telephone: '07294958549', }, @@ -142,7 +142,7 @@ describe('manufacturer api functions', () => { street_name: 'Example Street', town: 'Oxford', county: 'Oxfordshire', - postCode: 'OX1 2AB', + postcode: 'OX1 2AB', }, telephone: '07934303412', }, diff --git a/src/api/manufacturer.tsx b/src/api/manufacturer.tsx index 92dbcb811..79846bf9d 100644 --- a/src/api/manufacturer.tsx +++ b/src/api/manufacturer.tsx @@ -77,9 +77,7 @@ export const useAddManufacturer = (): UseMutationResult< ); }; -const deleteManufacturer = async ( - session: ViewManufacturerResponse -): Promise => { +const deleteManufacturer = async (session: Manufacturer): Promise => { let apiUrl: string; apiUrl = ''; const settingsResult = await settings; @@ -94,14 +92,15 @@ const deleteManufacturer = async ( export const useDeleteManufacturer = (): UseMutationResult< void, AxiosError, - ViewManufacturerResponse + Manufacturer > => { - return useMutation( - (session: ViewManufacturerResponse) => deleteManufacturer(session), - { - onError: (error) => { - console.log('Got error ' + error.message); - }, - } - ); + const queryClient = useQueryClient(); + return useMutation((session: Manufacturer) => deleteManufacturer(session), { + onError: (error) => { + console.log('Got error ' + error.message); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['Manufacturers'] }); + }, + }); }; diff --git a/src/manufacturer/DeleteManufacturerDialog.test.tsx b/src/manufacturer/DeleteManufacturerDialog.test.tsx index 57c5037ab..adb5ab8e2 100644 --- a/src/manufacturer/DeleteManufacturerDialog.test.tsx +++ b/src/manufacturer/DeleteManufacturerDialog.test.tsx @@ -4,12 +4,12 @@ import userEvent from '@testing-library/user-event'; import { renderComponentWithBrowserRouter } from '../setupTests'; import { DeleteManufacturerProps } from './deleteManufacturerDialog.component'; import DeleteManufacturerDialog from './deleteManufacturerDialog.component'; -import { ViewManufacturerResponse } from '../app.types'; +import { Manufacturer } from '../app.types'; describe('Delete Manufacturer Dialog', () => { const onClose = jest.fn(); let props: DeleteManufacturerProps; - let manufacturer: ViewManufacturerResponse; + let manufacturer: Manufacturer; let user; const createView = (): RenderResult => { return renderComponentWithBrowserRouter( @@ -25,7 +25,7 @@ describe('Delete Manufacturer Dialog', () => { street_name: 'Example Street', town: 'Oxford', county: 'Oxfordshire', - postCode: 'OX1 2AB', + postcode: 'OX1 2AB', }, telephone: '056896598', id: '1', @@ -93,7 +93,7 @@ describe('Delete Manufacturer Dialog', () => { await waitFor(() => { expect( screen.getByText( - 'The manufacturer is a part of a Catalogue Item, Please delete the Catalogue Item first Please delete the Catalogue Item first' + 'The specified manufacturer is a part of a Catalogue Item. Please delete the Catalogue Item first.' ) ).toBeInTheDocument(); }); diff --git a/src/manufacturer/deleteManufacturerDialog.component.tsx b/src/manufacturer/deleteManufacturerDialog.component.tsx index 131c6cabd..bd899c18e 100644 --- a/src/manufacturer/deleteManufacturerDialog.component.tsx +++ b/src/manufacturer/deleteManufacturerDialog.component.tsx @@ -8,14 +8,14 @@ import { FormHelperText, } from '@mui/material'; import React from 'react'; -import { ErrorParsing, ViewManufacturerResponse } from '../app.types'; +import { ErrorParsing, Manufacturer } from '../app.types'; import { AxiosError } from 'axios'; import { useDeleteManufacturer } from '../api/manufacturer'; export interface DeleteManufacturerProps { open: boolean; onClose: () => void; - manufacturer: ViewManufacturerResponse | undefined; + manufacturer: Manufacturer | undefined; } const DeleteManufacturerDialog = (props: DeleteManufacturerProps) => { @@ -45,7 +45,7 @@ const DeleteManufacturerDialog = (props: DeleteManufacturerProps) => { if (response && error.response?.status === 409) { setError(true); setErrorMessage( - `${response.detail} Please delete the Catalogue Item first` + `${response.detail}. Please delete the Catalogue Item first.` ); return; } diff --git a/src/manufacturer/manufacturer.component.test.tsx b/src/manufacturer/manufacturer.component.test.tsx index b0d2c0184..6018f8bb1 100644 --- a/src/manufacturer/manufacturer.component.test.tsx +++ b/src/manufacturer/manufacturer.component.test.tsx @@ -82,4 +82,24 @@ describe('Manufacturer', () => { ).toHaveStyle('background-color: inherit'); }); }); + + it('opens delete dialog and closes it correctly', async () => { + createView(); + await waitFor(() => { + expect(screen.getByText('Manufacturer A')).toBeInTheDocument(); + }); + + await user.click( + screen.getByRole('button', { name: 'Delete Manufacturer A manufacturer' }) + ); + + expect(screen.getByText('Delete Manufacturer')).toBeInTheDocument(); + + const closeButton = screen.getByRole('button', { name: 'Cancel' }); + await user.click(closeButton); + + await waitFor(() => { + expect(screen.getByText('Actions')).toBeInTheDocument(); + }); + }); }); diff --git a/src/manufacturer/manufacturer.component.tsx b/src/manufacturer/manufacturer.component.tsx index addb4c7bb..f11413e13 100644 --- a/src/manufacturer/manufacturer.component.tsx +++ b/src/manufacturer/manufacturer.component.tsx @@ -16,12 +16,12 @@ import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; import React from 'react'; import { useManufacturers } from '../api/manufacturer'; -import { ViewManufacturerResponse } from '../app.types'; +import { Manufacturer } from '../app.types'; import DeleteManufacturerDialog from './deleteManufacturerDialog.component'; import { ManufacturerDetail } from '../app.types'; import AddManufacturerDialog from './addManufacturerDialog.component'; -function Manufacturer() { +function ManufacturerComponent() { const [addManufacturer, setAddManufacturer] = React.useState({ name: '', @@ -45,7 +45,7 @@ function Manufacturer() { React.useState(false); const [selectedManufacturer, setSelectedManufacturer] = React.useState< - ViewManufacturerResponse | undefined + Manufacturer | undefined >(undefined); const [hoveredRow, setHoveredRow] = React.useState(null); @@ -227,4 +227,4 @@ function Manufacturer() { ); } -export default Manufacturer; +export default ManufacturerComponent; diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 0f160e3aa..0437ab323 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -159,8 +159,7 @@ export const handlers = [ return res( ctx.status(409), ctx.json({ - detail: - 'The manufacturer is a part of a Catalogue Item, Please delete the Catalogue Item first', + detail: 'The specified manufacturer is a part of a Catalogue Item', }) ); } else { From de6c201c8c024967dc46c2dc182e8e125423286c Mon Sep 17 00:00:00 2001 From: Matteo Guarnaccia Date: Wed, 8 Nov 2023 16:32:15 +0000 Subject: [PATCH 7/7] unit test suggested change --- src/manufacturer/manufacturer.component.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manufacturer/manufacturer.component.test.tsx b/src/manufacturer/manufacturer.component.test.tsx index 6018f8bb1..ca216857c 100644 --- a/src/manufacturer/manufacturer.component.test.tsx +++ b/src/manufacturer/manufacturer.component.test.tsx @@ -99,7 +99,7 @@ describe('Manufacturer', () => { await user.click(closeButton); await waitFor(() => { - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.queryByText('Delete Manufacturer')).not.toBeInTheDocument(); }); }); });