diff --git a/.yarn/patches/vitest-npm-3.2.4-7a07f931b1.patch b/.yarn/patches/vitest-npm-3.2.4-7a07f931b1.patch new file mode 100644 index 000000000..2872a2a0b --- /dev/null +++ b/.yarn/patches/vitest-npm-3.2.4-7a07f931b1.patch @@ -0,0 +1,54 @@ +diff --git a/dist/chunks/index.CmSc2RE5.js b/dist/chunks/index.CmSc2RE5.js +index 90004b3064c731a22f584000525e0bff3f423867..7fa0351ff36a5d21640520685420046298b71959 100644 +--- a/dist/chunks/index.CmSc2RE5.js ++++ b/dist/chunks/index.CmSc2RE5.js +@@ -3,8 +3,6 @@ import { Console } from 'node:console'; + // SEE https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/interfaces.js + const LIVING_KEYS = [ + "DOMException", +- "URL", +- "URLSearchParams", + "EventTarget", + "NamedNodeMap", + "Node", +@@ -161,9 +159,6 @@ const LIVING_KEYS = [ + "ShadowRoot", + "MutationObserver", + "MutationRecord", +- "Headers", +- "AbortController", +- "AbortSignal", + "Uint8Array", + "Uint16Array", + "Uint32Array", +@@ -441,9 +436,6 @@ var jsdom = { + // https://nodejs.org/dist/latest/docs/api/globals.html + const globalNames = [ + "structuredClone", +- "fetch", +- "Request", +- "Response", + "BroadcastChannel", + "MessageChannel", + "MessagePort", +@@ -454,6 +446,20 @@ var jsdom = { + const value = globalThis[name]; + if (typeof value !== "undefined" && typeof dom.window[name] === "undefined") dom.window[name] = value; + } ++ const overrideGlobals = [ ++ "fetch", ++ "Request", ++ "Response", ++ "Headers", ++ "AbortController", ++ "AbortSignal", ++ "URL", ++ "URLSearchParams", ++ ]; ++ for (const name of overrideGlobals) { ++ const value = globalThis[name]; ++ if (typeof value !== "undefined") dom.window[name] = value; ++ } + return { + getVmContext() { + return dom.getInternalVMContext(); diff --git a/package.json b/package.json index 2805b6db9..1c91ad91c 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,11 @@ "@types/js-cookie": "3.0.1", "@types/react-dom": "18.3.7", "@types/react-redux-toastr": "8.0.0", - "@types/react-router-dom": "5.3.3", "@types/redux-logger": "3.0.8", "@vitejs/plugin-react": "^5.0.0", - "axios": "1.13.5", + "axios": "1.13.6", "browserslist": "4.28.1", "browserslist-to-esbuild": "2.1.1", - "connected-react-router": "6.9.3", "husky": "9.1.7", "i18next": "25.10.3", "i18next-browser-languagedetector": "8.2.0", @@ -31,18 +29,17 @@ "js-cookie": "3.0.1", "loglevel": "1.9.1", "prettier": "3.8.1", - "prop-types": "15.8.1", "react": "18.3.1", "react-dom": "18.3.1", "react-i18next": "16.6.0", "react-joyride": "2.9.3", "react-redux": "8.1.2", "react-redux-toastr": "8.0.0", - "react-router-dom": "5.3.0", + "react-router": "7.13.1", "redux": "4.2.1", "redux-logger": "3.0.6", "redux-thunk": "3.1.0", - "single-spa": "5.9.4", + "single-spa": "6.0.3", "typeface-roboto": "1.1.13", "typescript": "5.9.2", "vite": "5.4.21" @@ -88,10 +85,8 @@ "@types/node": "24.12.0", "@types/react": "18.3.23", "@types/react-redux": "7.1.20", - "@types/react-router": "5.1.20", "@types/redux-mock-store": "1.5.0", - "@vitest/coverage-v8": "2.1.9", - "axios-mock-adapter": "1.22.0", + "@vitest/coverage-v8": "3.2.4", "concurrently": "9.2.0", "cookie-parser": "1.4.5", "cors": "2.8.5", @@ -116,7 +111,7 @@ "serve": "14.2.0", "start-server-and-test": "~2.1.0", "typescript-eslint": "8.57.0", - "vitest": "2.1.9", + "vitest": "patch:vitest@npm%3A3.2.4#~/.yarn/patches/vitest-npm-3.2.4-7a07f931b1.patch", "wait-on": "9.0.4" }, "packageManager": "yarn@4.13.0" diff --git a/src/App.test.tsx b/src/App.test.tsx index 03d73d6ed..661652762 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -2,6 +2,7 @@ import { useMediaQuery } from '@mui/material'; import { act, fireEvent, render, screen } from '@testing-library/react'; import axios from 'axios'; import { createRoot } from 'react-dom/client'; +import * as AppImport from './App'; import App, { AppSansHoc } from './App'; import { RegisterRouteType } from './state/scigateway.types'; import { flushPromises } from './testUtils'; @@ -88,10 +89,7 @@ describe('App', () => { ); window.matchMedia = vi.fn().mockReturnValue({ matches: true }); - Object.defineProperty(window, 'location', { - configurable: true, - value: { reload: vi.fn() }, - }); + const reloadSpy = vi.spyOn(AppImport, 'reload').mockReturnValue(); vi.useFakeTimers(); @@ -145,7 +143,7 @@ describe('App', () => { expect(screen.getByText('test message')).toBeInTheDocument(); // should not refresh page when maintenance state changes from false to true - expect(window.location.reload).not.toHaveBeenCalled(); + expect(reloadSpy).not.toHaveBeenCalled(); vi.mocked(axios.get).mockImplementation(() => Promise.resolve({ @@ -163,6 +161,6 @@ describe('App', () => { }); // should refresh page when maintenance state changes from true to false - expect(window.location.reload).toHaveBeenCalled(); + expect(reloadSpy).toHaveBeenCalled(); }); }); diff --git a/src/App.tsx b/src/App.tsx index e9de286ad..895d61f1d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,3 @@ -import { ConnectedRouter, routerMiddleware } from 'connected-react-router'; -import { createBrowserHistory } from 'history'; import * as log from 'loglevel'; import * as React from 'react'; import { WithTranslation, withTranslation } from 'react-i18next'; @@ -7,8 +5,6 @@ import { Provider } from 'react-redux'; import { AnyAction, applyMiddleware, compose, createStore } from 'redux'; import { createLogger } from 'redux-logger'; import { thunk, ThunkDispatch } from 'redux-thunk'; -import PageContainer from './pageContainer.component'; -import { Preloader } from './preloader/preloader.component'; import { configureSite, loadMaintenanceState, @@ -19,19 +15,16 @@ import ScigatewayMiddleware, { } from './state/middleware/scigateway.middleware'; import AppReducer from './state/reducers/App.reducer'; import { StateType } from './state/state.types'; -import { ConnectedThemeProvider } from './theming'; // This order needed for the App.css to apply to toasts correctly import ReduxToastr from 'react-redux-toastr'; +import { BrowserRouter } from 'react-router'; +import { reload as reloadPage } from './App'; import './App.css'; +import PageContainer from './pageContainer.component'; +import { Preloader } from './preloader/preloader.component'; +import { ConnectedThemeProvider } from './theming'; -const history = createBrowserHistory(); - -const middleware = [ - thunk, - routerMiddleware(history), - ScigatewayMiddleware, - autoLoginMiddleware, -]; +const middleware = [thunk, ScigatewayMiddleware, autoLoginMiddleware]; if (import.meta.env.MODE === 'development') { const logger = createLogger({ collapsed: true }); middleware.push(logger); @@ -46,7 +39,7 @@ const composeEnhancers = /* eslint-enable */ const store = createStore( - AppReducer(history), + AppReducer(), composeEnhancers(applyMiddleware(...middleware)) ); @@ -68,6 +61,12 @@ const toastrConfig = (): React.ReactElement => ( /> ); +// Have to have this as separate function and re-import this function +// to ensure we can mock this properly in unit tests +export function reload() { + window.location.reload(); +} + class App extends React.Component { public componentDidMount(): void { // Check for changes in maintenance state. Ensures that state changes are @@ -86,7 +85,7 @@ class App extends React.Component { // Reload the page if maintenance state changes from true to false if (storedMaintenanceState.show && !fetchedMaintenanceState.show) - window.location.reload(); + reloadPage(); } }); } @@ -99,18 +98,20 @@ class App extends React.Component { return (
- + {/* react-router transitions don't work nicely with external stores i.e. redux */} + {this.props.tReady ? ( <> {toastrConfig()} + ) : ( )} - +
); diff --git a/src/__snapshots__/pageContainer.test.tsx.snap b/src/__snapshots__/pageContainer.test.tsx.snap index 55895eb31..93adf72bb 100644 --- a/src/__snapshots__/pageContainer.test.tsx.snap +++ b/src/__snapshots__/pageContainer.test.tsx.snap @@ -186,6 +186,7 @@ exports[`PageContainer - Tests > renders correctly 1`] = ` > @@ -301,6 +302,7 @@ exports[`PageContainer - Tests > renders correctly 1`] = ` > renders correctly 1`] = ` style="background-image: url(/src/images/facility.jpg); background-repeat: no-repeat; background-position: bottom right; background-size: cover; width: 100%; height: 100%; border-radius: 4px;" >
@@ -368,6 +370,7 @@ exports[`PageContainer - Tests > renders correctly 1`] = ` >
renders correctly 1`] = ` > renders correctly 1`] = ` | Accessibilty statement diff --git a/src/accessibilityPage/accessibilityPage.component.test.tsx b/src/accessibilityPage/accessibilityPage.component.test.tsx index c47ac4501..0b8d019d1 100644 --- a/src/accessibilityPage/accessibilityPage.component.test.tsx +++ b/src/accessibilityPage/accessibilityPage.component.test.tsx @@ -1,14 +1,12 @@ -import React from 'react'; -import AccessibilityPage from './accessibilityPage.component'; -import { buildTheme } from '../theming'; import { StyledEngineProvider, ThemeProvider } from '@mui/material'; -import { StateType } from '../state/state.types'; -import { authState, initialState } from '../state/reducers/scigateway.reducer'; -import { createLocation } from 'history'; +import { render } from '@testing-library/react'; +import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; import { thunk } from 'redux-thunk'; -import { Provider } from 'react-redux'; -import { render } from '@testing-library/react'; +import { authState, initialState } from '../state/reducers/scigateway.reducer'; +import { StateType } from '../state/state.types'; +import { buildTheme } from '../theming'; +import AccessibilityPage from './accessibilityPage.component'; describe('Accessibility page component', () => { const theme = buildTheme(false); @@ -17,7 +15,6 @@ describe('Accessibility page component', () => { beforeEach(() => { state = { scigateway: { ...initialState, authorisation: { ...authState } }, - router: { location: createLocation('/') }, }; }); diff --git a/src/adminPage/adminPage.component.test.tsx b/src/adminPage/adminPage.component.test.tsx index a72cea5bc..b970f3912 100644 --- a/src/adminPage/adminPage.component.test.tsx +++ b/src/adminPage/adminPage.component.test.tsx @@ -1,15 +1,15 @@ import { StyledEngineProvider, ThemeProvider } from '@mui/material'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createLocation, createMemoryHistory, History } from 'history'; import React from 'react'; import { Provider } from 'react-redux'; -import { Router } from 'react-router'; +import { BrowserRouter, Route, Routes } from 'react-router'; import configureStore from 'redux-mock-store'; import { thunk } from 'redux-thunk'; import TestAuthProvider from '../authentication/testAuthProvider'; +import { makeRouteNonExact } from '../routing/routing.component'; import { authState, initialState } from '../state/reducers/scigateway.reducer'; -import { PluginConfig } from '../state/scigateway.types'; +import { PluginConfig, scigatewayRoutes } from '../state/scigateway.types'; import { StateType } from '../state/state.types'; import { buildTheme } from '../theming'; import AdminPage, { getAdminPluginRoutes } from './adminPage.component'; @@ -17,32 +17,34 @@ import AdminPage, { getAdminPluginRoutes } from './adminPage.component'; describe('Admin page component', () => { let mockStore; let state: StateType; - let history: History; beforeEach(() => { mockStore = configureStore([thunk]); - history = createMemoryHistory(); state = { scigateway: { ...initialState, authorisation: { ...authState } }, - router: { location: createLocation('/') }, }; state.scigateway.authorisation.provider = new TestAuthProvider(null); + window.history.replaceState(null, '', '/'); }); const theme = buildTheme(false); - function Wrapper({ - children, - }: { - children: React.ReactElement; - }): JSX.Element { + function Wrapper({ children }: { children: React.ReactNode }): JSX.Element { const testStore = mockStore(state); return ( - {children} + + {/* Emulate being inside the main router to ensure relative routing works */} + + {children}} + /> + + @@ -66,7 +68,7 @@ describe('Admin page component', () => { admin: true, }, ]; - history.replace('/admin/maintenance'); + window.history.replaceState(null, '', '/admin/maintenance'); render(, { wrapper: Wrapper }); @@ -97,7 +99,7 @@ describe('Admin page component', () => { }, ]; state.scigateway.adminPageDefaultTab = 'maintenance'; - history.replace('/admin/download'); + window.history.replaceState(null, '', '/admin/download'); render(, { wrapper: Wrapper }); @@ -127,19 +129,19 @@ describe('Admin page component', () => { admin: true, }, ]; - history.replace('/admin/maintenance'); + window.history.replaceState(null, '', '/admin/maintenance'); const user = userEvent.setup(); render(, { wrapper: Wrapper }); await user.click(screen.getByRole('tab', { name: 'Admin Download' })); - expect(history.location.pathname).toEqual('/admin/download'); + expect(window.location.pathname).toEqual('/admin/download'); expect( screen.getByRole('tabpanel', { name: 'Admin Download' }) ).toBeInTheDocument(); await user.click(screen.getByRole('tab', { name: 'Maintenance' })); - expect(history.location.pathname).toEqual('/admin/maintenance'); + expect(window.location.pathname).toEqual('/admin/maintenance'); expect( await screen.findByRole('tabpanel', { name: 'Maintenance' }) ).toBeInTheDocument(); @@ -147,7 +149,7 @@ describe('Admin page component', () => { it("falls back to 'maintenance' when adminPageDefaultTab is not provided", () => { state.scigateway.adminPageDefaultTab = undefined; - history.replace('/admin'); + window.history.replaceState(null, '', '/admin'); render(, { wrapper: Wrapper }); @@ -170,7 +172,7 @@ describe('Admin page component', () => { }, ]; state.scigateway.adminPageDefaultTab = 'maintenance'; - history.replace('/admin/test'); + window.history.replaceState(null, '', '/admin/test'); render(, { wrapper: Wrapper }); @@ -183,7 +185,7 @@ describe('Admin page component', () => { it("falls back to 'maintenance' when adminPageDefaultTab doesn't match any key in adminRoutes", () => { state.scigateway.adminPageDefaultTab = 'nonexistentTab'; - history.replace('/admin'); + window.history.replaceState(null, '', '/admin'); render(, { wrapper: Wrapper }); diff --git a/src/adminPage/adminPage.component.tsx b/src/adminPage/adminPage.component.tsx index 1436f047a..9fd05e352 100644 --- a/src/adminPage/adminPage.component.tsx +++ b/src/adminPage/adminPage.component.tsx @@ -2,16 +2,17 @@ import { Paper } from '@mui/material'; import Typography from '@mui/material/Typography'; import React, { ReactElement } from 'react'; import { connect } from 'react-redux'; -import { PluginConfig } from '../state/scigateway.types'; +import { PluginConfig, scigatewayRoutes } from '../state/scigateway.types'; import { StateType } from '../state/state.types'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; import { useTranslation } from 'react-i18next'; -import { Link, Route, Switch, useLocation } from 'react-router-dom'; +import { Link, Route, Routes, useLocation } from 'react-router'; import PageNotFound from '../pageNotFound/pageNotFound.component'; import { getAdminRoutes, + makeRouteNonExact, PluginPlaceHolder, } from '../routing/routing.component'; import MaintenancePage from './maintenancePage.component'; @@ -109,35 +110,46 @@ const AdminPage = (props: AdminPageProps): ReactElement => { ); })} - - - - + + + } /> + ); }; diff --git a/src/adminPage/maintenancePage.component.test.tsx b/src/adminPage/maintenancePage.component.test.tsx index 60b14bf76..dc2330d11 100644 --- a/src/adminPage/maintenancePage.component.test.tsx +++ b/src/adminPage/maintenancePage.component.test.tsx @@ -1,4 +1,3 @@ -import { createLocation } from 'history'; import React from 'react'; import configureStore, { MockStore } from 'redux-mock-store'; import { authState, initialState } from '../state/reducers/scigateway.reducer'; @@ -8,7 +7,7 @@ import { StyledEngineProvider, ThemeProvider } from '@mui/material'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; -import { MemoryRouter } from 'react-router'; +import { BrowserRouter } from 'react-router'; import { thunk } from 'redux-thunk'; import TestAuthProvider from '../authentication/testAuthProvider'; import { @@ -27,7 +26,6 @@ describe('maintenance page component', () => { mockStore = configureStore([thunk]); state = { scigateway: { ...initialState, authorisation: { ...authState } }, - router: { location: createLocation('/admin') }, }; state.scigateway.authorisation.provider = new TestAuthProvider(null); @@ -36,18 +34,12 @@ describe('maintenance page component', () => { const theme = buildTheme(false); - function Wrapper({ - children, - }: { - children: React.ReactElement; - }): JSX.Element { + function Wrapper({ children }: { children: React.ReactNode }): JSX.Element { return ( - - {children} - + {children} diff --git a/src/authentication/testAuthProvider.tsx b/src/authentication/testAuthProvider.tsx index 696fe4682..f939e84ac 100644 --- a/src/authentication/testAuthProvider.tsx +++ b/src/authentication/testAuthProvider.tsx @@ -2,12 +2,12 @@ import { MaintenanceState, ScheduledMaintenanceState, } from '../state/scigateway.types'; -import { AuthProvider } from '../state/state.types'; +import { AuthProvider, User } from '../state/state.types'; export default class TestAuthProvider implements AuthProvider { private token: string | null; public redirectUrl: string | null; - public user = null; + public user: User | null = null; public mnemonic: string | undefined; public authUrl: string | undefined; public autoLogin?: () => Promise; @@ -32,6 +32,14 @@ export default class TestAuthProvider implements AuthProvider { public logIn(username: string, password: string): Promise { if (username === 'username' && password === 'password') { this.token = 'validLoginToken'; + this.user = { username, isAdmin: true, avatarUrl: '' }; + return Promise.resolve(); + } else if ( + username === 'username_with_avatar_url' && + password === 'password' + ) { + this.token = 'validLoginToken'; + this.user = { username, isAdmin: true, avatarUrl: 'test_url' }; return Promise.resolve(); } diff --git a/src/cookieConsent/cookieConsent.component.test.tsx b/src/cookieConsent/cookieConsent.component.test.tsx index bf91c83fe..10a3a9c01 100644 --- a/src/cookieConsent/cookieConsent.component.test.tsx +++ b/src/cookieConsent/cookieConsent.component.test.tsx @@ -1,18 +1,17 @@ import React from 'react'; -import CookieConsent from './cookieConsent.component'; -import { StateType } from '../state/state.types'; -import configureStore, { MockStore } from 'redux-mock-store'; -import { authState, initialState } from '../state/reducers/scigateway.reducer'; -import { initialiseAnalytics } from '../state/actions/scigateway.actions'; -import { Provider } from 'react-redux'; -import { buildTheme } from '../theming'; import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; -import Cookies from 'js-cookie'; -import { createLocation } from 'history'; -import { push } from 'connected-react-router'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import Cookies from 'js-cookie'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router'; +import configureStore, { MockStore } from 'redux-mock-store'; +import { initialiseAnalytics } from '../state/actions/scigateway.actions'; +import { authState, initialState } from '../state/reducers/scigateway.reducer'; +import { StateType } from '../state/state.types'; +import { buildTheme } from '../theming'; +import CookieConsent from './cookieConsent.component'; describe('Cookie consent component', () => { let mockStore; @@ -23,28 +22,26 @@ describe('Cookie consent component', () => { mockStore = configureStore(); state = { scigateway: { ...initialState, authorisation: { ...authState } }, - router: { location: createLocation('/') }, }; state.scigateway.siteLoading = false; state.scigateway.analytics = { id: 'test id', initialised: false, }; + window.history.replaceState(null, '', '/'); store = mockStore(state); }); const theme = buildTheme(false); - function Wrapper({ - children, - }: { - children: React.ReactElement; - }): JSX.Element { + function Wrapper({ children }: { children: React.ReactNode }): JSX.Element { return ( - {children} + + {children} + ); @@ -71,8 +68,8 @@ describe('Cookie consent component', () => { screen.getByRole('button', { name: 'manage-preferences-button' }) ); - expect(store.getActions().length).toEqual(1); - expect(store.getActions()[0]).toEqual(push('/cookies')); + expect(window.window.history.length).toEqual(2); + expect(window.location.pathname).toEqual('/cookies'); }); it('should set cookie to true upon user accept', async () => { @@ -147,7 +144,8 @@ describe('Cookie consent component', () => { }); it('should set open to false if on /cookies page', () => { - state.router = { location: createLocation('/cookies') }; + window.history.replaceState(null, '', '/cookies'); + store = mockStore(state); render(, { wrapper: Wrapper }); diff --git a/src/cookieConsent/cookieConsent.component.tsx b/src/cookieConsent/cookieConsent.component.tsx index ebf9ae338..a191dfb82 100644 --- a/src/cookieConsent/cookieConsent.component.tsx +++ b/src/cookieConsent/cookieConsent.component.tsx @@ -1,17 +1,15 @@ -import React from 'react'; -import { styled } from '@mui/material/styles'; import Button from '@mui/material/Button'; import Snackbar from '@mui/material/Snackbar'; +import { styled } from '@mui/material/styles'; import Cookies from 'js-cookie'; +import React from 'react'; +import { connect } from 'react-redux'; +import { useLocation, useNavigate } from 'react-router'; +import { Action, Dispatch } from 'redux'; import { initialiseAnalytics } from '../state/actions/scigateway.actions'; -import { Action } from 'redux'; +import { AppStrings, scigatewayRoutes } from '../state/scigateway.types'; import { AnalyticsState, StateType } from '../state/state.types'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { getAppStrings, getString } from '../state/strings'; -import { AppStrings } from '../state/scigateway.types'; -import { push } from 'connected-react-router'; -import { Location } from 'history'; const ManageButton = styled(Button)(({ theme }) => ({ color: '#FFFFFF', @@ -26,13 +24,11 @@ const AcceptButton = styled(Button)(({ theme }) => ({ interface CookieConsentStateProps { analytics?: AnalyticsState; res: AppStrings | undefined; - location: Location; loading: boolean; } interface CookieConsentDispatchProps { initialiseAnalytics: () => Action; - navigateToCookies: () => Action; } export type CombinedCookieConsentProps = CookieConsentStateProps & @@ -43,6 +39,9 @@ export const CookieConsent = ( ): React.ReactElement => { const [open, setOpen] = React.useState(false); + const location = useLocation(); + const navigate = useNavigate(); + React.useEffect(() => { const consentCookie = JSON.parse(Cookies.get('cookie-consent') ?? 'null'); if ( @@ -72,13 +71,13 @@ export const CookieConsent = ( if ( props.loading || JSON.parse(Cookies.get('cookie-consent') ?? 'null') || - props.location.pathname === '/cookies' + location.pathname === scigatewayRoutes.cookies ) { setOpen(false); } else { setOpen(true); } - }, [props]); + }, [location.pathname, props]); const handleAccept = ( _event: React.SyntheticEvent | React.MouseEvent @@ -110,7 +109,7 @@ export const CookieConsent = ( key="decline" variant="outlined" size="small" - onClick={props.navigateToCookies} + onClick={() => navigate(scigatewayRoutes.cookies)} > {getString(props.res, 'manage-preferences-button')} , @@ -131,7 +130,6 @@ export const CookieConsent = ( const mapStateToProps = (state: StateType): CookieConsentStateProps => ({ analytics: state.scigateway.analytics, res: getAppStrings(state, 'cookie-consent'), - location: state.router.location, loading: state.scigateway.siteLoading, }); @@ -139,7 +137,6 @@ const mapDispatchToProps = ( dispatch: Dispatch ): CookieConsentDispatchProps => ({ initialiseAnalytics: () => dispatch(initialiseAnalytics()), - navigateToCookies: () => dispatch(push('/cookies')), }); export const UnconnectedCookieConsent = CookieConsent; diff --git a/src/cookieConsent/cookiesPage.component.test.tsx b/src/cookieConsent/cookiesPage.component.test.tsx index 1cc6f97ac..1da37bac3 100644 --- a/src/cookieConsent/cookiesPage.component.test.tsx +++ b/src/cookieConsent/cookiesPage.component.test.tsx @@ -1,16 +1,15 @@ +import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { TOptionsBase } from 'i18next'; +import Cookies from 'js-cookie'; import React from 'react'; -import CookiesPage from './cookiesPage.component'; -import { StateType } from '../state/state.types'; +import { BrowserRouter } from 'react-router'; import configureStore, { MockStore } from 'redux-mock-store'; import { authState, initialState } from '../state/reducers/scigateway.reducer'; +import { StateType } from '../state/state.types'; import { buildTheme } from '../theming'; -import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; -import Cookies from 'js-cookie'; -import { createLocation } from 'history'; -import { push } from 'connected-react-router'; -import { TOptionsBase } from 'i18next'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import CookiesPage from './cookiesPage.component'; vi.mock('react-i18next', () => ({ useTranslation: () => { @@ -30,7 +29,6 @@ describe('Cookies page component', () => { mockStore = configureStore(); state = { scigateway: { ...initialState, authorisation: { ...authState } }, - router: { location: createLocation('/cookies') }, }; store = mockStore(state); @@ -40,14 +38,12 @@ describe('Cookies page component', () => { const theme = buildTheme(false); - function Wrapper({ - children, - }: { - children: React.ReactElement; - }): JSX.Element { + function Wrapper({ children }: { children: React.ReactNode }): JSX.Element { return ( - {children} + + {children} + ); } @@ -79,8 +75,7 @@ describe('Cookies page component', () => { expect(callArguments[0]).toEqual('cookie-consent'); expect(callArguments[1]).toEqual(JSON.stringify({ analytics: true })); - expect(store.getActions().length).toEqual(1); - expect(store.getActions()[0]).toEqual(push('/')); + expect(window.location.pathname).toEqual('/'); }); it('should remove cookies when user revokes consent', async () => { @@ -116,7 +111,6 @@ describe('Cookies page component', () => { expect(mockCookiesRemove.calls[0][0]).toEqual('_ga'); expect(mockCookiesRemove.calls[1][0]).toEqual('_gid'); - expect(store.getActions().length).toEqual(1); - expect(store.getActions()[0]).toEqual(push('/')); + expect(window.location.pathname).toEqual('/'); }); }); diff --git a/src/cookieConsent/cookiesPage.component.tsx b/src/cookieConsent/cookiesPage.component.tsx index aee0e5f1a..5131504d2 100644 --- a/src/cookieConsent/cookiesPage.component.tsx +++ b/src/cookieConsent/cookiesPage.component.tsx @@ -1,17 +1,16 @@ -import React from 'react'; -import Typography from '@mui/material/Typography'; -import Switch from '@mui/material/Switch'; import Button from '@mui/material/Button'; import Grid from '@mui/material/Grid'; import { styled } from '@mui/material/styles'; -import { getAppStrings, getString } from '../state/strings'; -import { connect } from 'react-redux'; -import { StateType } from '../state/state.types'; -import { AppStrings } from '../state/scigateway.types'; +import Switch from '@mui/material/Switch'; +import Typography from '@mui/material/Typography'; import Cookies from 'js-cookie'; -import { Dispatch, Action } from 'redux'; -import { push } from 'connected-react-router'; +import React from 'react'; import { useTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import { useNavigate } from 'react-router'; +import { AppStrings, scigatewayRoutes } from '../state/scigateway.types'; +import { StateType } from '../state/state.types'; +import { getAppStrings, getString } from '../state/strings'; const RootDiv = styled('div')(({ theme }) => ({ padding: theme.spacing(2), @@ -49,12 +48,7 @@ interface CookiesPageProps { res: AppStrings | undefined; } -interface CookiesPageDispatchProps { - navigateToHome: () => Action; -} - -export type CombinedCookiesPageProps = CookiesPageProps & - CookiesPageDispatchProps; +export type CombinedCookiesPageProps = CookiesPageProps; const handleSavePreferences = ({ analytics }: { analytics: boolean }): void => { if (!analytics) { @@ -75,6 +69,7 @@ const CookiesPage = (props: CombinedCookiesPageProps): React.ReactElement => { cookieConsent ? cookieConsent.analytics : false ); const { t } = useTranslation(); + const navigate = useNavigate(); return ( @@ -187,7 +182,7 @@ const CookiesPage = (props: CombinedCookiesPageProps): React.ReactElement => { sx={{ color: 'primary.contrastText' }} onClick={() => { handleSavePreferences({ analytics }); - props.navigateToHome(); + navigate(scigatewayRoutes.home); }} > {getString(props.res, 'save-preferences-button')} @@ -200,10 +195,6 @@ const mapStateToProps = (state: StateType): CookiesPageProps => ({ res: getAppStrings(state, 'cookies-page'), }); -const mapDispatchToProps = (dispatch: Dispatch): CookiesPageDispatchProps => ({ - navigateToHome: () => dispatch(push('/')), -}); - export const UnconnectedCookiesPage = CookiesPage; -export default connect(mapStateToProps, mapDispatchToProps)(CookiesPage); +export default connect(mapStateToProps)(CookiesPage); diff --git a/src/footer/__snapshots__/footer.component.test.tsx.snap b/src/footer/__snapshots__/footer.component.test.tsx.snap index 742033878..ccc2b1cb0 100644 --- a/src/footer/__snapshots__/footer.component.test.tsx.snap +++ b/src/footer/__snapshots__/footer.component.test.tsx.snap @@ -31,6 +31,7 @@ exports[`Footer component > footer renders correctly 1`] = ` | Accessibilty statement diff --git a/src/footer/footer.component.test.tsx b/src/footer/footer.component.test.tsx index 7aba36371..2d9045323 100644 --- a/src/footer/footer.component.test.tsx +++ b/src/footer/footer.component.test.tsx @@ -1,17 +1,17 @@ import { ThemeProvider } from '@mui/material'; import { render } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; +import { BrowserRouter } from 'react-router'; import { buildTheme } from '../theming'; import Footer from './footer.component'; describe('Footer component', () => { it('footer renders correctly', () => { const { asFragment } = render( - +