diff --git a/cypress/e2e/catalogue/catalogueCategory.cy.ts b/cypress/e2e/catalogue/catalogueCategory.cy.ts index 52b2adf06..b2fed902b 100644 --- a/cypress/e2e/catalogue/catalogueCategory.cy.ts +++ b/cypress/e2e/catalogue/catalogueCategory.cy.ts @@ -275,15 +275,10 @@ describe('Catalogue Category', () => { }); it('when root has no data it displays no catagories error message', () => { - cy.window().then(async (window) => { - // Reference global instances set in "src/mocks/browser.js". - - const { worker, rest } = window.msw; - worker.use( - rest.get('/v1/catalogue-categories/', (req, res, ctx) => { - return res(ctx.status(200), ctx.json([])); - }) - ); + cy.editEndpointResponse({ + url: '/v1/catalogue-categories/', + data: [], + statusCode: 200, }); cy.findByText( 'There are no catalogue categories. Please add a category using the plus icon in the top left of your screen' diff --git a/cypress/e2e/catalogue/catalogueItems.cy.ts b/cypress/e2e/catalogue/catalogueItems.cy.ts index 84528accb..1f3b11281 100644 --- a/cypress/e2e/catalogue/catalogueItems.cy.ts +++ b/cypress/e2e/catalogue/catalogueItems.cy.ts @@ -118,13 +118,13 @@ describe('Catalogue Items', () => { cy.findByRole('button', { name: 'Save' }).click(); cy.findByText( - 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links are accepted' + 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links with typical top-level domain are accepted' ).should('exist'); cy.findByLabelText('Manufacturer URL *').clear(); cy.findByLabelText('Manufacturer URL *').type('https://test.co.uk'); cy.findByText( - 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links are accepted' + 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links with typical top-level domain are accepted' ).should('not.exist'); }); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index cba8d3ceb..e7d950bb5 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -43,6 +43,18 @@ Cypress.Commands.add('clearMocks', () => { mockedRequests = []; }); +Cypress.Commands.add('editEndpointResponse', ({ url, data, statusCode }) => { + cy.window().then((window) => { + const { worker, rest } = window.msw; + + worker.use( + rest.get(url, (req, res, ctx) => { + return res(ctx.status(statusCode), ctx.json(data)); + }) + ); + }); +}); + Cypress.Commands.add('startSnoopingBrowserMockedRequest', () => { cy.window().then((window) => { const worker = window?.msw?.worker; @@ -91,12 +103,12 @@ declare global { * Clear all mocks * @example cy.clearMocks() */ - clearMocks(): Chainable> + clearMocks(): Chainable>; /** * Use before findBrowserMockedRequests for checking specific requests were sent * @example cy.startSnoopingBrowserMockedRequest() */ - startSnoopingBrowserMockedRequest(): Chainable> + startSnoopingBrowserMockedRequest(): Chainable>; /** * Returns a request that was recorded after 'startSnoopingBrowserMockedRequest' was called * @@ -113,7 +125,17 @@ declare global { ); }); */ - findBrowserMockedRequests({method, url}: any): Chainable + editEndpointResponse({ url, data, statusCode }: any): Chainable; + /** + * Edits the response of the endpoint request + * + * @example cy.editEndpointResponse({ + url: '/v1/catalogue-categories/', + data: [], + statusCode: 200, + }); + */ + findBrowserMockedRequests({ method, url }: any): Chainable; } } } diff --git a/src/api/catalogueItem.tsx b/src/api/catalogueItem.tsx index 1de5c890f..932117d04 100644 --- a/src/api/catalogueItem.tsx +++ b/src/api/catalogueItem.tsx @@ -151,6 +151,7 @@ export const useDeleteCatalogueItem = (): UseMutationResult< }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['CatalogueItems'] }); + queryClient.removeQueries({ queryKey: ['CatalogueItem'] }); }, } ); diff --git a/src/catalogue/items/catalogueItemsDialog.component.test.tsx b/src/catalogue/items/catalogueItemsDialog.component.test.tsx index 8880480a2..47e61083f 100644 --- a/src/catalogue/items/catalogueItemsDialog.component.test.tsx +++ b/src/catalogue/items/catalogueItemsDialog.component.test.tsx @@ -254,7 +254,7 @@ describe('Catalogue Items Dialog', () => { expect( screen.getByText( - 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links are accepted' + 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links with typical top-level domain are accepted' ) ).toBeInTheDocument(); @@ -349,7 +349,7 @@ describe('Catalogue Items Dialog', () => { expect(onClose).toHaveBeenCalled(); }); - it('display error message when invalid number format', async () => { + it('display error message when invalid number format in property values and invalid manufacturer url', async () => { props = { ...props, parentId: '4', @@ -399,7 +399,7 @@ describe('Catalogue Items Dialog', () => { expect( screen.getByText( - 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links are accepted' + 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links with typical top-level domain are accepted' ) ).toBeInTheDocument(); }); @@ -724,7 +724,7 @@ describe('Catalogue Items Dialog', () => { ); }); - it('displays error message if no form fields have been changed', async () => { + it('displays error message if no fields have been changed (when they are no catalogue property fields)', async () => { props = { ...props, parentId: '1', diff --git a/src/catalogue/items/catalogueItemsDialog.component.tsx b/src/catalogue/items/catalogueItemsDialog.component.tsx index 3ec08b509..55048a9a6 100644 --- a/src/catalogue/items/catalogueItemsDialog.component.tsx +++ b/src/catalogue/items/catalogueItemsDialog.component.tsx @@ -58,7 +58,10 @@ export interface CatalogueItemsDialogProps { function isValidUrl(url: string) { try { const parsedUrl = new URL(url); - return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'; + return ( + (parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:') && + parsedUrl.hostname.includes('.') // Checks for the typical top-level domain + ); } catch (error) { return false; } @@ -213,7 +216,7 @@ function CatalogueItemsDialog(props: CatalogueItemsDialogProps) { setManufacturerWebUrlErrorMessage( !catalogueItemManufacturer.web_url.trim() ? 'Please enter a Manufacturer URL' - : 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links are accepted' + : 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links with typical top-level domain are accepted' ); hasErrors = true; } @@ -354,7 +357,7 @@ function CatalogueItemsDialog(props: CatalogueItemsDialogProps) { setManufacturerWebUrlErrorMessage( !catalogueItemManufacturer.web_url.trim() ? 'Please enter a Manufacturer URL' - : 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links are accepted' + : 'Please enter a valid Manufacturer URL. Only "http://" and "https://" links with typical top-level domain are accepted' ); hasErrors = true; } diff --git a/src/catalogue/items/catalogueItemsLandingPage.component.test.tsx b/src/catalogue/items/catalogueItemsLandingPage.component.test.tsx index 886eed5e0..20a06e93c 100644 --- a/src/catalogue/items/catalogueItemsLandingPage.component.test.tsx +++ b/src/catalogue/items/catalogueItemsLandingPage.component.test.tsx @@ -73,6 +73,14 @@ describe('Catalogue Items Landing Page', () => { ).toBeInTheDocument(); }); }); + + it('shows the loading indicator', async () => { + createView('/inventory-management-system/catalogue/items/1'); + + await waitFor(() => { + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + }); + }); it('toggles the manufacturer so it is either visible or hidden', async () => { createView('/inventory-management-system/catalogue/items/1'); await waitFor(() => { @@ -95,7 +103,7 @@ describe('Catalogue Items Landing Page', () => { }); }); - it('opens the edit catalogue item dialog', async () => { + it('opens and closes the edit catalogue item dialog', async () => { createView('/inventory-management-system/catalogue/items/1'); await waitFor(() => { diff --git a/src/catalogue/items/catalogueItemsLandingPage.component.tsx b/src/catalogue/items/catalogueItemsLandingPage.component.tsx index cd665a240..6768fb7e4 100644 --- a/src/catalogue/items/catalogueItemsLandingPage.component.tsx +++ b/src/catalogue/items/catalogueItemsLandingPage.component.tsx @@ -7,7 +7,13 @@ import ListItem from '@mui/material/ListItem'; import ListItemText from '@mui/material/ListItemText'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; -import { Box, Button, Collapse, Link as MuiLink } from '@mui/material'; +import { + Box, + Button, + Collapse, + LinearProgress, + Link as MuiLink, +} from '@mui/material'; import { useCatalogueCategoryById } from '../../api/catalogueCategory'; import CatalogueItemsDialog from './catalogueItemsDialog.component'; import { @@ -257,19 +263,25 @@ function CatalogueItemsLandingPage() { )} - {!catalogueItemIdData && !catalogueItemIdDataLoading && ( - - No result found - - This item doesn't exist. Please click the Home button to navigate to - the catalogue home - + {!catalogueItemIdDataLoading ? ( + !catalogueItemIdData && ( + + No result found + + This item doesn't exist. Please click the Home button to navigate + to the catalogue home + + + ) + ) : ( + + )} diff --git a/src/catalogue/items/catalogueItemsTable.component.test.tsx b/src/catalogue/items/catalogueItemsTable.component.test.tsx index 914b68f6c..b51aecfc9 100644 --- a/src/catalogue/items/catalogueItemsTable.component.test.tsx +++ b/src/catalogue/items/catalogueItemsTable.component.test.tsx @@ -122,7 +122,7 @@ describe('Catalogue Items Table', () => { }); }); - it('opens the delete catalogue item dialog', async () => { + it('opens the delete catalogue item dialog and can delete an item', async () => { createView(); await waitFor(() => { @@ -136,6 +136,7 @@ describe('Catalogue Items Table', () => { const deleteButton = screen.getByRole('button', { name: 'Delete Energy Meters 26 catalogue item', }); + await user.click(deleteButton); await waitFor(() => { diff --git a/src/index.tsx b/src/index.tsx index d7cca1586..8945a8334 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -174,12 +174,25 @@ function prepare() { } const settings = fetchSettings(); + setSettings(settings); if ( - process.env.NODE_ENV === 'development' || - process.env.REACT_APP_E2E_TESTING + process.env.NODE_ENV === 'development' && + !process.env.REACT_APP_E2E_TESTING ) { + settings + .then((settings) => { + if (settings && settings.apiUrl !== '') { + render(); + } else { + prepare().then(() => render()); + } + }) + .catch((error) => log.error(`Got error: ${error.message}`)); + + log.setDefaultLevel(log.levels.DEBUG); +} else if (process.env.REACT_APP_E2E_TESTING) { prepare().then(() => render()); log.setDefaultLevel(log.levels.DEBUG); } else { diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 3e5fc49aa..f11ccafb2 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -131,7 +131,7 @@ export const handlers = [ }) ); } else { - return res(ctx.status(200), ctx.json('')); + return res(ctx.status(204)); } } else { return res(ctx.status(400), ctx.json('')); @@ -191,7 +191,7 @@ export const handlers = [ }) ); } else { - return res(ctx.status(200), ctx.json('')); + return res(ctx.status(204)); } } else { return res(ctx.status(400), ctx.json(''));