diff --git a/src/App.tsx b/src/App.tsx index a0778c3cb..5877dfe1a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,21 +1,26 @@ +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'; +import type { Router } from '@remix-run/router'; import { QueryCache, QueryClient, QueryClientProvider, } from '@tanstack/react-query'; -import React from 'react'; -// import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'; -import type { Router } from '@remix-run/router'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { AxiosError } from 'axios'; import { enGB } from 'date-fns/locale/en-GB'; +import React from 'react'; import { RouterProvider, createBrowserRouter, type RouteObject, } from 'react-router-dom'; -import AdminPage from './admin/admin.component'; +import AdminCardView from './admin/adminCardView.component'; +import AdminLayout, { + AdminErrorComponent, +} from './admin/adminLayout.component'; +import Units from './admin/units/units.component'; +import UsageStatuses from './admin/usageStatuses/usageStatuses.component'; import { clearFailedAuthRequestsQueue, retryFailedAuthRequests, @@ -49,7 +54,9 @@ import ViewTabs from './view/viewTabs.component'; export const paths = { any: '*', root: '/', - admin: '/admin-ims/*', + admin: '/admin-ims', + adminUnits: '/admin-ims/units', + adminUsageStatuses: '/admin-ims/usage-statuses', homepage: '/ims', catalogue: '/catalogue/*', systems: '/systems/*', @@ -85,7 +92,19 @@ const routeObject: RouteObject[] = [ children: [ { path: paths.root, Component: HomePage }, { path: paths.homepage, Component: HomePage }, - { path: paths.admin, Component: AdminPage }, + { + path: paths.admin, + Component: AdminLayout, + children: [ + { index: true, Component: AdminCardView }, + { path: paths.adminUnits, Component: Units }, + { path: paths.adminUsageStatuses, Component: UsageStatuses }, + { + path: '*', + Component: AdminErrorComponent, + }, + ], + }, { path: paths.catalogue, Component: Catalogue }, { path: paths.catalogueItem, @@ -100,11 +119,14 @@ const routeObject: RouteObject[] = [ { path: paths.manufacturers, Component: ManufacturerLayout, - loader: manufacturerLayoutLoader(queryClient), ErrorBoundary: ManufacturerLayoutErrorComponent, children: [ { index: true, Component: ManufacturerTable }, - { path: paths.manufacturer, Component: ManufacturerLandingPage }, + { + path: paths.manufacturer, + Component: ManufacturerLandingPage, + loader: manufacturerLayoutLoader(queryClient), + }, { path: '*', Component: ManufacturerErrorComponent, @@ -164,7 +186,7 @@ export function Layout() { } > - {/* */} + diff --git a/src/admin/__snapshots__/adminCardView.component.test.tsx.snap b/src/admin/__snapshots__/adminCardView.component.test.tsx.snap new file mode 100644 index 000000000..56b271aad --- /dev/null +++ b/src/admin/__snapshots__/adminCardView.component.test.tsx.snap @@ -0,0 +1,86 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AdminCardView > renders admin card view correctly 1`] = ` + + + +`; diff --git a/src/admin/__snapshots__/adminLayout.component.test.tsx.snap b/src/admin/__snapshots__/adminLayout.component.test.tsx.snap new file mode 100644 index 000000000..d72044ac0 --- /dev/null +++ b/src/admin/__snapshots__/adminLayout.component.test.tsx.snap @@ -0,0 +1,248 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Admin Error Component > renders Admin error page correctly 1`] = ` + +
+

+ Invalid Admin Route +

+

+ The admin route you are trying to access doesn't exist. Please click the Home button to navigate back to the Admin Home page. +

+
+
+`; + +exports[`Admin Layout > renders admin layout page correctly 1`] = ` + +
+
+
+ + + + +
+
+
+
+`; + +exports[`Admin Layout > renders units breadcrumbs correctly 1`] = ` + +
+
+
+ + + + +
+
+
+
+`; + +exports[`Admin Layout > renders usage statuses breadcrumbs correctly 1`] = ` + +
+
+
+ + + + +
+
+
+
+`; diff --git a/src/admin/admin.component.test.tsx b/src/admin/admin.component.test.tsx deleted file mode 100644 index 88e6250a1..000000000 --- a/src/admin/admin.component.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { screen, waitFor } from '@testing-library/react'; -import { renderComponentWithRouterProvider } from '../testUtils'; -import AdminPage from './admin.component'; - -const mockedUseNavigate = vi.fn(); - -vi.mock('react-router-dom', async () => ({ - ...(await vi.importActual('react-router-dom')), - useNavigate: () => mockedUseNavigate, -})); - -describe('AdminPage', () => { - const createView = (path: string) => { - return renderComponentWithRouterProvider(, 'admin', path); - }; - - it('renders admin page correctly', async () => { - createView('/admin-ims'); - - await waitFor(() => { - expect(screen.getByText('Units')).toBeInTheDocument(); - }); - expect(screen.getByText('Usage Statuses')).toBeInTheDocument(); - }); - - it('renders no results page for invalid url', async () => { - createView('/admin-ims/testFake'); - - await waitFor(() => { - expect(screen.getByText('No results found')).toBeInTheDocument(); - }); - expect( - screen.getByText( - `The admin URL route you're trying to access doesn't exist. Please return to the homepage by clicking the home button at the top left of your screen.` - ) - ).toBeInTheDocument(); - }); -}); diff --git a/src/admin/admin.component.tsx b/src/admin/admin.component.tsx deleted file mode 100644 index 87bae5d64..000000000 --- a/src/admin/admin.component.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { - Box, - Button, - Card, - CardContent, - Grid, - Typography, -} from '@mui/material'; -import React from 'react'; -import { Link, useLocation, useNavigate } from 'react-router-dom'; -import { BreadcrumbsInfo } from '../api/api.types'; -import Breadcrumbs from '../view/breadcrumbs.component'; -import Units from './units/units.component'; -import UsageStatuses from './usageStatuses/usageStatuses.component'; - -export const useNavigateToAdminFunction = () => { - const navigate = useNavigate(); - - return React.useCallback( - (newPath: string | null) => { - navigate(`/admin-ims${newPath ? `/${newPath}` : ''}`); - }, - [navigate] - ); -}; - -// returns the admin function from the path (null when just on adminPage) -export const useGetAdminPageName = (): string | null => { - const location = useLocation(); - - return React.useMemo(() => { - let adminPageName: string | null = location.pathname.replace( - '/admin-ims', - '' - ); - adminPageName = - adminPageName === '' ? null : adminPageName.replace('/', ''); - return adminPageName; - }, [location.pathname]); -}; - -const adminBreadCrumbsTrails: { [key: string]: [string, string] } = { - ['units']: ['units', 'Units'], - ['usage-statuses']: ['usage-statuses', 'Usage statuses'], -}; - -function AdminPage() { - const navigateToAdminFunction = useNavigateToAdminFunction(); - const adminPageName = useGetAdminPageName(); - - const adminBreadCrumbs: BreadcrumbsInfo | undefined = adminPageName - ? { - trail: [adminBreadCrumbsTrails[adminPageName] ?? ['', '']], - full_trail: true, - } - : undefined; - - return ( - - -
- - navigateToAdminFunction(null)} - homeLocation="Admin" - /> - -
-
- - {adminPageName === null && ( - - - - - - - - - - - )} - - {adminPageName === 'units' && } - {adminPageName === 'usage-statuses' && } - {adminPageName !== null && - adminPageName !== 'units' && - adminPageName !== 'usage-statuses' && ( - - - No results found - - - {`The admin URL route you're trying to access doesn't exist. Please return to the homepage by clicking the home button at the top left of your screen.`} - - - )} -
- ); -} - -export default AdminPage; diff --git a/src/admin/adminCardView.component.test.tsx b/src/admin/adminCardView.component.test.tsx new file mode 100644 index 000000000..a9566fe2d --- /dev/null +++ b/src/admin/adminCardView.component.test.tsx @@ -0,0 +1,18 @@ +import { screen, waitFor } from '@testing-library/react'; +import { renderComponentWithRouterProvider } from '../testUtils'; +import AdminCardView from './adminCardView.component'; + +describe('AdminCardView', () => { + const createView = () => { + return renderComponentWithRouterProvider(); + }; + it('renders admin card view correctly', async () => { + const view = createView(); + + await waitFor(() => { + expect(screen.getByText('Units')).toBeInTheDocument(); + }); + + expect(view.asFragment()).toMatchSnapshot(); + }); +}); diff --git a/src/admin/adminCardView.component.tsx b/src/admin/adminCardView.component.tsx new file mode 100644 index 000000000..660b818a4 --- /dev/null +++ b/src/admin/adminCardView.component.tsx @@ -0,0 +1,93 @@ +import { Button, Card, CardContent, Grid, Typography } from '@mui/material'; +import { Link } from 'react-router-dom'; + +function AdminCardView() { + return ( + + + + + + + + + + + + + ); +} + +export default AdminCardView; diff --git a/src/admin/adminLayout.component.test.tsx b/src/admin/adminLayout.component.test.tsx new file mode 100644 index 000000000..08f0dcb02 --- /dev/null +++ b/src/admin/adminLayout.component.test.tsx @@ -0,0 +1,90 @@ +import { screen, waitFor } from '@testing-library/react'; +import userEvent, { UserEvent } from '@testing-library/user-event'; +import { paths } from '../App'; +import { renderComponentWithRouterProvider } from '../testUtils'; +import AdminLayout, { AdminErrorComponent } from './adminLayout.component'; + +const mockedUseNavigate = vi.fn(); + +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), + useNavigate: () => mockedUseNavigate, +})); + +describe('Admin Layout', () => { + let user: UserEvent; + + beforeEach(() => { + user = userEvent.setup(); + }); + const createView = (path: string, urlPathKey: keyof typeof paths) => { + return renderComponentWithRouterProvider(, urlPathKey, path); + }; + + it('renders admin layout page correctly', async () => { + const view = createView('/admin-ims', 'admin'); + + await waitFor(() => { + expect( + screen.getByRole('button', { + name: 'navigate to admin home', + }) + ).toBeInTheDocument(); + }); + + expect(view.asFragment()).toMatchSnapshot(); + }); + + it('renders units breadcrumbs correctly', async () => { + const view = createView('/admin-ims/units', 'adminUnits'); + + await waitFor(() => { + expect(screen.getByText('Units')).toBeInTheDocument(); + }); + + expect(view.asFragment()).toMatchSnapshot(); + }); + + it('renders usage statuses breadcrumbs correctly', async () => { + const view = createView('/admin-ims/usage-statuses', 'adminUsageStatuses'); + + await waitFor(() => { + expect(screen.getByText('Usage statuses')).toBeInTheDocument(); + }); + + expect(view.asFragment()).toMatchSnapshot(); + }); + + it('calls useNavigate when the home button is clicked', async () => { + createView('/admin-ims/usage-statuses', 'adminUsageStatuses'); + + await waitFor(() => { + expect(screen.getByText('Usage statuses')).toBeInTheDocument(); + }); + + const homeButton = screen.getByRole('button', { + name: 'navigate to admin home', + }); + + await user.click(homeButton); + + expect(mockedUseNavigate).toHaveBeenCalledTimes(1); + expect(mockedUseNavigate).toHaveBeenCalledWith('/admin-ims'); + }); +}); + +describe('Admin Error Component', () => { + const createView = () => { + return renderComponentWithRouterProvider(); + }; + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('renders Admin error page correctly', async () => { + const view = createView(); + + expect(view.asFragment()).toMatchSnapshot(); + }); +}); diff --git a/src/admin/adminLayout.component.tsx b/src/admin/adminLayout.component.tsx new file mode 100644 index 000000000..d0f31630d --- /dev/null +++ b/src/admin/adminLayout.component.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Outlet, useLocation } from 'react-router-dom'; +import { BreadcrumbsInfo } from '../api/api.types'; +import { paths } from '../App'; +import BaseLayoutHeader from '../common/baseLayoutHeader.component'; +import ErrorPage from '../common/errorPage.component'; + +export const AdminErrorComponent = () => { + return ( + + ); +}; + +// returns the admin function from the path (null when just on adminPage) +export const useGetAdminPageName = (): string | null => { + const location = useLocation(); + + return React.useMemo(() => { + const adminPageName: string | null = location.pathname.replace( + paths.admin, + '' + ); + return adminPageName === '' ? null : adminPageName.replace('/', ''); + }, [location.pathname]); +}; + +const adminBreadCrumbsTrails: { [key: string]: [string, string] } = { + ['units']: ['units', 'Units'], + ['usage-statuses']: ['usage-statuses', 'Usage statuses'], +}; + +function AdminLayout() { + const adminPageName = useGetAdminPageName(); + + const adminBreadCrumbs: BreadcrumbsInfo | undefined = adminPageName + ? { + trail: [adminBreadCrumbsTrails[adminPageName] ?? ['', '']], + full_trail: true, + } + : undefined; + + return ( + + + + ); +} + +export default AdminLayout; diff --git a/src/admin/units/__snapshots__/units.component.test.tsx.snap b/src/admin/units/__snapshots__/units.component.test.tsx.snap index 22d2c0ecd..9554870ed 100644 --- a/src/admin/units/__snapshots__/units.component.test.tsx.snap +++ b/src/admin/units/__snapshots__/units.component.test.tsx.snap @@ -214,32 +214,6 @@ exports[`Units > renders table correctly 1`] = ` -
-
-
- - - - -
-
-
renders table correctly 1`] = `
-
-
-
- - - - -
-
-
{ it('renders table correctly', async () => { const view = createView(); + await waitFor(() => { + expect(screen.queryByRole('progressbar')).not.toBeInTheDocument(); + }); + await waitFor(() => { expect(screen.getByText('megapixels')).toBeInTheDocument(); });