diff --git a/src/__mocks__/axios.ts b/__mocks__/axios.ts similarity index 88% rename from src/__mocks__/axios.ts rename to __mocks__/axios.ts index bfbffc10..91ed4589 100644 --- a/src/__mocks__/axios.ts +++ b/__mocks__/axios.ts @@ -1,7 +1,7 @@ // TODO: move __mocks__ folder back to root once facebook/create-react-app#7539 is fixed const requests = { - get: jest.fn((path) => { + get: vi.fn((path) => { if (path === '/settings.json') { return Promise.resolve({ data: { @@ -20,7 +20,7 @@ const requests = { }); } }), - post: jest.fn(() => Promise.resolve({ data: {} })), + post: vi.fn(() => Promise.resolve({ data: {} })), }; export default requests; diff --git a/src/__mocks__/react-i18next.jsx b/__mocks__/react-i18next.jsx similarity index 100% rename from src/__mocks__/react-i18next.jsx rename to __mocks__/react-i18next.jsx diff --git a/src/App.test.tsx b/src/App.test.tsx index b09d4924..492b9d9c 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,37 +1,36 @@ -import React from 'react'; +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 App, { AppSansHoc } from './App'; -import { act, fireEvent, render, screen } from '@testing-library/react'; import { flushPromises } from './setupTests'; -import axios from 'axios'; import { RegisterRouteType } from './state/scigateway.types'; -import { useMediaQuery } from '@mui/material'; -jest.mock('./state/actions/loadMicroFrontends', () => ({ - init: jest.fn(() => Promise.resolve()), +vi.mock('./state/actions/loadMicroFrontends', () => ({ + init: vi.fn(() => Promise.resolve()), singleSpaPluginRoutes: ['/plugin1'], })); -jest.mock('@mui/material', () => ({ +vi.mock('@mui/material', async () => ({ __esmodule: true, - ...jest.requireActual('@mui/material'), - useMediaQuery: jest.fn(), + ...(await vi.importActual('@mui/material')), + useMediaQuery: vi.fn(), })); const testToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QifQ.hNQI_r8BATy1LyXPr6Zuo9X_V0kSED8ngcqQ6G-WV5w'; // needed for the maintenance state update test - for some reason it doesn't work when at the beginning of the test itself -window.localStorage.__proto__.getItem = jest.fn().mockImplementation((name) => { +window.localStorage.__proto__.getItem = vi.fn().mockImplementation((name) => { return name === 'scigateway:token' ? testToken : null; }); describe('App', () => { beforeEach(() => { - jest.mocked(useMediaQuery).mockReturnValue(true); + vi.mocked(useMediaQuery).mockReturnValue(true); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('renders without crashing', () => { @@ -46,27 +45,27 @@ describe('App', () => { }); it('should show preloader when react-i18next is not ready', () => { - render(); + render(); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); it('should dispatch loadMaintenanceState and force refresh the page when maintenance changes', async () => { // mock so token verify succeeds - (axios.post as jest.Mock).mockImplementation(() => + (axios.post as vi.Mock).mockImplementation(() => Promise.resolve({ data: {}, }) ); - window.matchMedia = jest.fn().mockReturnValue({ matches: true }); + window.matchMedia = vi.fn().mockReturnValue({ matches: true }); Object.defineProperty(window, 'location', { configurable: true, - value: { reload: jest.fn() }, + value: { reload: vi.fn() }, }); - jest.useFakeTimers(); + vi.useFakeTimers(); - render(); + render(); const registerRouteAction = { type: RegisterRouteType, @@ -94,7 +93,7 @@ describe('App', () => { expect(screen.queryByText('Maintenance')).not.toBeInTheDocument(); - (axios.get as jest.Mock).mockImplementation(() => + (axios.get as vi.Mock).mockImplementation(() => Promise.resolve({ data: { show: true, @@ -104,7 +103,7 @@ describe('App', () => { ); act(() => { - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); }); await act(async () => { @@ -117,7 +116,7 @@ describe('App', () => { // should not refresh page when maintenance state changes from false to true expect(window.location.reload).not.toHaveBeenCalled(); - (axios.get as jest.Mock).mockImplementation(() => + (axios.get as vi.Mock).mockImplementation(() => Promise.resolve({ data: { show: false, @@ -126,7 +125,7 @@ describe('App', () => { }) ); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); await act(async () => { await flushPromises(); diff --git a/src/authentication/githubAuthProvider.test.tsx b/src/authentication/githubAuthProvider.test.tsx index 1a3df830..8f68a3f2 100644 --- a/src/authentication/githubAuthProvider.test.tsx +++ b/src/authentication/githubAuthProvider.test.tsx @@ -7,14 +7,14 @@ describe('github auth provider', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QifQ.hNQI_r8BATy1LyXPr6Zuo9X_V0kSED8ngcqQ6G-WV5w'; beforeEach(() => { - jest.spyOn(window.localStorage.__proto__, 'getItem'); - window.localStorage.__proto__.getItem = jest + vi.spyOn(window.localStorage.__proto__, 'getItem'); + window.localStorage.__proto__.getItem = vi .fn() .mockImplementation((name) => name === 'scigateway:token' ? testToken : null ); - window.localStorage.__proto__.removeItem = jest.fn(); - window.localStorage.__proto__.setItem = jest.fn(); + window.localStorage.__proto__.removeItem = vi.fn(); + window.localStorage.__proto__.setItem = vi.fn(); authProvider = new GithubAuthProvider('http://localhost:8000'); }); @@ -32,7 +32,7 @@ describe('github auth provider', () => { }); it('should call the api to verify code', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + (mockAxios.post as vi.Mock).mockImplementation(() => Promise.resolve({ data: { token: testToken, @@ -63,7 +63,7 @@ describe('github auth provider', () => { }); it('should log the user out if code is invalid', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + (mockAxios.post as vi.Mock).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -80,7 +80,7 @@ describe('github auth provider', () => { }); it('should log the user out if the token has expired', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + (mockAxios.post as vi.Mock).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -97,7 +97,7 @@ describe('github auth provider', () => { }); it('should return user information if token is valid', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + (mockAxios.post as vi.Mock).mockImplementation(() => Promise.resolve({ data: { username: 'test_user', diff --git a/src/authentication/icatAuthProvider.test.tsx b/src/authentication/icatAuthProvider.test.tsx index 08eeeb07..0bb6be23 100644 --- a/src/authentication/icatAuthProvider.test.tsx +++ b/src/authentication/icatAuthProvider.test.tsx @@ -3,7 +3,7 @@ import ICATAuthProvider from './icatAuthProvider'; import parseJwt from './parseJwt'; import { BroadcastSignOutType } from '../state/scigateway.types'; -jest.mock('./parseJwt'); +vi.mock('./parseJwt'); describe('ICAT auth provider', () => { let icatAuthProvider: ICATAuthProvider; @@ -22,8 +22,8 @@ describe('ICAT auth provider', () => { return null; } }); - window.localStorage.__proto__.removeItem = jest.fn(); - window.localStorage.__proto__.setItem = jest.fn(); + window.localStorage.__proto__.removeItem = vi.fn(); + window.localStorage.__proto__.setItem = vi.fn(); icatAuthProvider = new ICATAuthProvider( 'mnemonic', @@ -35,7 +35,7 @@ describe('ICAT auth provider', () => { `{"sessionId": "${token}", "username": "${token} username", "userIsAdmin": true}` ); - document.dispatchEvent = jest.fn(); + document.dispatchEvent = vi.fn(); }); it('should set the mnemonic to empty string if none is provided (after autologin)', async () => { @@ -160,7 +160,7 @@ describe('ICAT auth provider', () => { ); // ensure token is null - window.localStorage.__proto__.getItem = jest.fn().mockReturnValue(null); + window.localStorage.__proto__.getItem = vi.fn().mockReturnValue(null); icatAuthProvider = new ICATAuthProvider( undefined, @@ -196,7 +196,7 @@ describe('ICAT auth provider', () => { ); // ensure token is null - window.localStorage.__proto__.getItem = jest.fn().mockReturnValue(null); + window.localStorage.__proto__.getItem = vi.fn().mockReturnValue(null); icatAuthProvider = new ICATAuthProvider( undefined, @@ -372,7 +372,7 @@ describe('ICAT auth provider', () => { it('should call api to set scheduled maintenance state', async () => { const scheduledMaintenanceState = { show: true, message: 'test' }; - mockAxios.put = jest.fn().mockImplementation(() => + mockAxios.put = vi.fn().mockImplementation(() => Promise.resolve({ data: 'test', }) @@ -393,7 +393,7 @@ describe('ICAT auth provider', () => { it('should log the user out if it fails to set scheduled maintenance state', async () => { const scheduledMaintenanceState = { show: true, message: 'test' }; - mockAxios.put = jest.fn().mockImplementation(() => + mockAxios.put = vi.fn().mockImplementation(() => Promise.reject({ response: { status: 401, @@ -427,7 +427,7 @@ describe('ICAT auth provider', () => { it('should log the user out if it fails to set maintenance state', async () => { const maintenanceState = { show: true, message: 'test' }; - mockAxios.put = jest.fn().mockImplementation(() => + mockAxios.put = vi.fn().mockImplementation(() => Promise.reject({ response: { status: 401, diff --git a/src/authentication/jwtAuthProvider.test.tsx b/src/authentication/jwtAuthProvider.test.tsx index 6b85b0dc..d596b7a1 100644 --- a/src/authentication/jwtAuthProvider.test.tsx +++ b/src/authentication/jwtAuthProvider.test.tsx @@ -7,14 +7,14 @@ describe('jwt auth provider', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJ1c2VySXNBZG1pbiI6ZmFsc2V9.PEuKaAD98doFTLyqcNFpsuv50AQR8ejrbDQ0pwazM7Q'; beforeEach(() => { - jest.spyOn(window.localStorage.__proto__, 'getItem'); + vi.spyOn(window.localStorage.__proto__, 'getItem'); window.localStorage.__proto__.getItem = jest .fn() .mockImplementation((name) => name === 'scigateway:token' ? testToken : null ); - window.localStorage.__proto__.removeItem = jest.fn(); - window.localStorage.__proto__.setItem = jest.fn(); + window.localStorage.__proto__.removeItem = vi.fn(); + window.localStorage.__proto__.setItem = vi.fn(); jwtAuthProvider = new JWTAuthProvider('http://localhost:8000'); }); diff --git a/src/cookieConsent/cookieConsent.component.test.tsx b/src/cookieConsent/cookieConsent.component.test.tsx index 18d85f36..f7092f67 100644 --- a/src/cookieConsent/cookieConsent.component.test.tsx +++ b/src/cookieConsent/cookieConsent.component.test.tsx @@ -76,7 +76,7 @@ describe('Cookie consent component', () => { }); it('should set cookie to true upon user accept', async () => { - Cookies.set = jest.fn(); + Cookies.set = vi.fn(); const user = userEvent.setup(); render(, { wrapper: Wrapper }); @@ -91,7 +91,7 @@ describe('Cookie consent component', () => { }); it("initalises analytics if cookie consent is true but analytics hasn't yet been initialised", () => { - jest.spyOn(document.head, 'appendChild'); + vi.spyOn(document.head, 'appendChild'); Cookies.get = jest .fn() diff --git a/src/cookieConsent/cookiesPage.component.test.tsx b/src/cookieConsent/cookiesPage.component.test.tsx index 93151cd4..2985ec42 100644 --- a/src/cookieConsent/cookiesPage.component.test.tsx +++ b/src/cookieConsent/cookiesPage.component.test.tsx @@ -12,7 +12,7 @@ import { TOptionsBase } from 'i18next'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -jest.mock('react-i18next', () => ({ +vi.mock('react-i18next', () => ({ useTranslation: () => { return { t: (key: string, options: TOptionsBase) => @@ -34,8 +34,8 @@ describe('Cookies page component', () => { }; store = mockStore(state); - Cookies.set = jest.fn(); - Cookies.remove = jest.fn(); + Cookies.set = vi.fn(); + Cookies.remove = vi.fn(); }); const theme = buildTheme(false); diff --git a/src/helpPage/helpPage.component.test.tsx b/src/helpPage/helpPage.component.test.tsx index bc74f19c..378439cc 100644 --- a/src/helpPage/helpPage.component.test.tsx +++ b/src/helpPage/helpPage.component.test.tsx @@ -8,9 +8,9 @@ import { render } from '@testing-library/react'; import { ThemeProvider } from '@mui/material'; import { buildTheme } from '../theming'; -jest.mock('../hooks/useAnchor', () => ({ +vi.mock('../hooks/useAnchor', () => ({ __esModule: true, - default: jest.fn(), + default: vi.fn(), })); describe('Help page component', () => { diff --git a/src/homePage/homePage.component.test.tsx b/src/homePage/homePage.component.test.tsx index e0e06fc6..7ade97b4 100644 --- a/src/homePage/homePage.component.test.tsx +++ b/src/homePage/homePage.component.test.tsx @@ -6,10 +6,10 @@ import { ThemeProvider } from '@mui/material'; import { buildTheme } from '../theming'; import { MemoryRouter } from 'react-router-dom'; -jest.mock('@mui/material', () => ({ +vi.mock('@mui/material', () => ({ __esmodule: true, ...jest.requireActual('@mui/material'), - useMediaQuery: jest.fn(() => true), + useMediaQuery: vi.fn(() => true), })); describe('Home page component', () => { diff --git a/src/hooks/useAnchor.test.tsx b/src/hooks/useAnchor.test.tsx index ca29a761..3673e0cd 100644 --- a/src/hooks/useAnchor.test.tsx +++ b/src/hooks/useAnchor.test.tsx @@ -25,10 +25,10 @@ const MOCK_REACT_ROUTER_LOCATION: Partial = { }; // mock implementation of useLocation to return the mock URL -jest.mock('react-router', () => ({ +vi.mock('react-router', () => ({ __esModule: true, ...jest.requireActual('react-router'), - useLocation: jest.fn(), + useLocation: vi.fn(), })); describe('useAnchor', () => { @@ -54,12 +54,12 @@ describe('useAnchor', () => { router: { location: createLocation('/') }, }); - const mockScrollIntoView = jest.fn(); + const mockScrollIntoView = vi.fn(); // pretend an element is found that matches the fragment // the weird type cast is to get around TypeScript error saying // the object is missing a bunch of other properties // we obviously don't care about them so there's no point in stubbing them. - jest.spyOn(document, 'getElementById').mockReturnValueOnce({ + vi.spyOn(document, 'getElementById').mockReturnValueOnce({ scrollIntoView: mockScrollIntoView, } as unknown as HTMLDivElement); @@ -83,10 +83,10 @@ describe('useAnchor', () => { router: { location: createLocation('/') }, }); - const mockScrollIntoView = jest.fn(); + const mockScrollIntoView = vi.fn(); // pretend no element with #fragment is found // and pretend there is other elements with IDs != fragment - jest.spyOn(document, 'getElementById').mockImplementation((id) => + vi.spyOn(document, 'getElementById').mockImplementation((id) => id === 'fragment' ? null : ({ @@ -117,12 +117,12 @@ describe('useAnchor', () => { router: { location: createLocation('/') }, }); - const mockScrollIntoView = jest.fn(); + const mockScrollIntoView = vi.fn(); // pretend an element is found that matches the fragment // the weird type cast is to get around TypeScript error saying // the object is missing a bunch of other properties // we obviously don't care about them so there's no point in stubbing them. - jest.spyOn(document, 'getElementById').mockReturnValueOnce({ + vi.spyOn(document, 'getElementById').mockReturnValueOnce({ scrollIntoView: mockScrollIntoView, } as unknown as HTMLDivElement); diff --git a/src/loginPage/loginPage.component.test.tsx b/src/loginPage/loginPage.component.test.tsx index f567ac27..eb3b7fc1 100644 --- a/src/loginPage/loginPage.component.test.tsx +++ b/src/loginPage/loginPage.component.test.tsx @@ -34,7 +34,7 @@ import { import userEvent from '@testing-library/user-event'; import { Router } from 'react-router-dom'; -jest.mock('loglevel'); +vi.mock('loglevel'); describe('Login selector component', () => { let props: CombinedLoginProps; @@ -48,8 +48,8 @@ describe('Login selector component', () => { provider: new TestAuthProvider(null), }, res: undefined, - verifyUsernameAndPassword: jest.fn(), - resetAuthState: jest.fn(), + verifyUsernameAndPassword: vi.fn(), + resetAuthState: vi.fn(), }; }); @@ -65,7 +65,7 @@ describe('Login selector component', () => { }, ]; const user = userEvent.setup(); - const testSetMnemonic = jest.fn(); + const testSetMnemonic = vi.fn(); render( { }, res: undefined, verifyUsernameAndPassword: () => Promise.resolve(), - resetAuthState: jest.fn(), + resetAuthState: vi.fn(), }; state.scigateway.authorisation = props.auth; @@ -385,7 +385,7 @@ describe('Login page component', () => { }); it('on submit verification method should be called with username and password arguments', async () => { - const mockLoginfn = jest.fn(() => Promise.resolve()); + const mockLoginfn = vi.fn(() => Promise.resolve()); const user = userEvent.setup(); props.verifyUsernameAndPassword = mockLoginfn; @@ -447,7 +447,7 @@ describe('Login page component', () => { history.replace('/login?token=test_token'); const promise = Promise.resolve(); - const mockLoginfn = jest.fn(() => promise); + const mockLoginfn = vi.fn(() => promise); props.verifyUsernameAndPassword = mockLoginfn; render(, { wrapper: Wrapper }); @@ -465,7 +465,7 @@ describe('Login page component', () => { }); it('on submit verification method should be called when logs in via keyless authenticator', async () => { - const mockLoginfn = jest.fn(() => Promise.resolve()); + const mockLoginfn = vi.fn(() => Promise.resolve()); const user = userEvent.setup(); props.verifyUsernameAndPassword = mockLoginfn; props.auth.provider.mnemonic = 'nokeys'; diff --git a/src/mainAppBar/mainAppBar.component.test.tsx b/src/mainAppBar/mainAppBar.component.test.tsx index a1aefd2e..2be44e6b 100644 --- a/src/mainAppBar/mainAppBar.component.test.tsx +++ b/src/mainAppBar/mainAppBar.component.test.tsx @@ -21,10 +21,10 @@ import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useMediaQuery } from '@mui/material'; -jest.mock('@mui/material', () => ({ +vi.mock('@mui/material', () => ({ __esmodule: true, ...jest.requireActual('@mui/material'), - useMediaQuery: jest.fn(), + useMediaQuery: vi.fn(), })); describe('Main app bar component', () => { @@ -66,7 +66,7 @@ describe('Main app bar component', () => { // I don't think MediaQuery works properly in jest // in the implementation useMediaQuery is used to query whether the current viewport is md or larger // here we assume it is always the case. - jest.mocked(useMediaQuery).mockReturnValue(true); + vi.mocked(useMediaQuery).mockReturnValue(true); }); const theme = buildTheme(false); @@ -441,7 +441,7 @@ describe('Main app bar component', () => { describe('mobile variant', () => { beforeEach(() => { - jest.mocked(useMediaQuery).mockReturnValue(false); + vi.mocked(useMediaQuery).mockReturnValue(false); }); it('shows drawer button, logo, user avatar, notification button, and an overflow menu button', () => { diff --git a/src/mainAppBar/mobileOverflowMenu.component.test.tsx b/src/mainAppBar/mobileOverflowMenu.component.test.tsx index be298589..5b1de964 100644 --- a/src/mainAppBar/mobileOverflowMenu.component.test.tsx +++ b/src/mainAppBar/mobileOverflowMenu.component.test.tsx @@ -55,7 +55,7 @@ describe('Mobile overflow menu', () => { }); it('combines app bar buttons and settings menu', () => { - render(, { + render(, { wrapper: Wrapper, }); @@ -87,7 +87,7 @@ describe('Mobile overflow menu', () => { it('redirects to Admin page when Admin button clicked (maintenance is default)', async () => { const user = userEvent.setup(); - render(, { + render(, { wrapper: Wrapper, }); @@ -101,7 +101,7 @@ describe('Mobile overflow menu', () => { state.scigateway.adminPageDefaultTab = 'download'; const user = userEvent.setup(); - render(, { + render(, { wrapper: Wrapper, }); @@ -114,7 +114,7 @@ describe('Mobile overflow menu', () => { it('toggles tutorial help when tutorial menu item is clicked', async () => { const user = userEvent.setup(); - render(, { + render(, { wrapper: Wrapper, }); @@ -129,7 +129,7 @@ describe('Mobile overflow menu', () => { 'token123' ); - render(, { + render(, { wrapper: Wrapper, }); @@ -141,7 +141,7 @@ describe('Mobile overflow menu', () => { it('hides help apge item when help page is disabled', () => { state.scigateway.features.showHelpPageButton = false; - render(, { + render(, { wrapper: Wrapper, }); diff --git a/src/navigationDrawer/navigationDrawer.component.test.tsx b/src/navigationDrawer/navigationDrawer.component.test.tsx index 65882ffd..29d3f38e 100644 --- a/src/navigationDrawer/navigationDrawer.component.test.tsx +++ b/src/navigationDrawer/navigationDrawer.component.test.tsx @@ -18,10 +18,10 @@ import ScigatewayReducer, { initialState as scigatewayInitialState, } from '../state/reducers/scigateway.reducer'; -jest.mock('@mui/material', () => ({ +vi.mock('@mui/material', () => ({ __esmodule: true, ...jest.requireActual('@mui/material'), - useMediaQuery: jest.fn(), + useMediaQuery: vi.fn(), })); describe('Navigation drawer component', () => { @@ -34,7 +34,7 @@ describe('Navigation drawer component', () => { // I don't think MediaQuery works properly in jest // in the implementation useMediaQuery is used to query whether the current viewport is md or larger // here we assume it is always the case. - jest.mocked(useMediaQuery).mockReturnValue(true); + vi.mocked(useMediaQuery).mockReturnValue(true); }); function Wrapper({ children }: { children: React.ReactNode }): JSX.Element { diff --git a/src/notifications/scigatewayNotification.component.test.tsx b/src/notifications/scigatewayNotification.component.test.tsx index 7e8a4327..b8975f6f 100644 --- a/src/notifications/scigatewayNotification.component.test.tsx +++ b/src/notifications/scigatewayNotification.component.test.tsx @@ -14,7 +14,7 @@ function createScigatewayNotification( message={message} severity={severity} index={0} - dismissNotification={jest.fn()} + dismissNotification={vi.fn()} /> ); } @@ -65,7 +65,7 @@ describe('Scigateway Notification component', () => { }); it('an action is fired when Scigateway Notification button is clicked', async () => { - const mockDismissFn = jest.fn(); + const mockDismissFn = vi.fn(); const user = userEvent.setup(); render( diff --git a/src/pageContainer.test.tsx b/src/pageContainer.test.tsx index f31320ac..0ede8e8f 100644 --- a/src/pageContainer.test.tsx +++ b/src/pageContainer.test.tsx @@ -16,10 +16,10 @@ import { buildTheme } from './theming'; import { toastr } from 'react-redux-toastr'; import userEvent from '@testing-library/user-event'; -jest.mock('@mui/material', () => ({ +vi.mock('@mui/material', () => ({ __esmodule: true, ...jest.requireActual('@mui/material'), - useMediaQuery: jest.fn(() => true), + useMediaQuery: vi.fn(() => true), })); describe('PageContainer - Tests', () => { @@ -47,7 +47,7 @@ describe('PageContainer - Tests', () => { }); it('calls toastr.clean() when escape is clicked', async () => { - const cleanSpy = jest.spyOn(toastr, 'clean'); + const cleanSpy = vi.spyOn(toastr, 'clean'); render( diff --git a/src/routing/authorisedRoute.component.test.tsx b/src/routing/authorisedRoute.component.test.tsx index 60152922..5800ae42 100644 --- a/src/routing/authorisedRoute.component.test.tsx +++ b/src/routing/authorisedRoute.component.test.tsx @@ -168,7 +168,7 @@ describe('AuthorisedRoute component', () => { it('renders non admin component when non admin user accesses it', () => { const testAuthProvider = new TestAuthProvider('test-token'); - testAuthProvider.isAdmin = jest.fn().mockImplementationOnce(() => false); + testAuthProvider.isAdmin = vi.fn().mockImplementationOnce(() => false); state.scigateway.authorisation.provider = testAuthProvider; state.scigateway.siteLoading = false; state.scigateway.authorisation.loading = false; @@ -192,7 +192,7 @@ describe('AuthorisedRoute component', () => { it('renders PageNotFound component when non admin user accesses admin component', () => { const testAuthProvider = new TestAuthProvider('test-token'); - testAuthProvider.isAdmin = jest.fn().mockImplementationOnce(() => false); + testAuthProvider.isAdmin = vi.fn().mockImplementationOnce(() => false); state.scigateway.authorisation.provider = testAuthProvider; state.scigateway.siteLoading = false; state.scigateway.authorisation.loading = false; diff --git a/src/routing/routing.component.test.tsx b/src/routing/routing.component.test.tsx index 3c74e127..86f5c67d 100644 --- a/src/routing/routing.component.test.tsx +++ b/src/routing/routing.component.test.tsx @@ -13,18 +13,18 @@ import { buildTheme } from '../theming'; import { act, render } from '@testing-library/react'; import { Router } from 'react-router'; -jest.mock('../adminPage/adminPage.component', () => () => 'Mocked AdminPage'); -jest.mock( +vi.mock('../adminPage/adminPage.component', () => () => 'Mocked AdminPage'); +vi.mock( '../maintenancePage/maintenancePage.component', () => () => 'Mocked MaintenancePage' ); -jest.mock('../preloader/preloader.component', () => ({ +vi.mock('../preloader/preloader.component', () => ({ Preloader: () => 'Mocked Preloader', })); -jest.mock('@mui/material', () => ({ +vi.mock('@mui/material', () => ({ __esmodule: true, ...jest.requireActual('@mui/material'), - useMediaQuery: jest.fn(), + useMediaQuery: vi.fn(), })); describe('Routing component', () => { @@ -61,7 +61,7 @@ describe('Routing component', () => { // I don't think MediaQuery works properly in jest // in the implementation useMediaQuery is used to query whether the current viewport is md or larger // here we assume it is always the case. - jest.mocked(useMediaQuery).mockReturnValue(true); + vi.mocked(useMediaQuery).mockReturnValue(true); }); afterEach(() => { @@ -172,7 +172,7 @@ describe('Routing component', () => { it('renders a route for maintenance page when site is under maintenance and user is not admin', () => { const testAuthProvider = new TestAuthProvider('logged in'); - testAuthProvider.isAdmin = jest.fn().mockImplementationOnce(() => false); + testAuthProvider.isAdmin = vi.fn().mockImplementationOnce(() => false); state.scigateway.authorisation.provider = testAuthProvider; state.scigateway.siteLoading = false; state.scigateway.maintenance = { show: true, message: 'test' }; @@ -406,11 +406,11 @@ describe('Routing component', () => { ]; state.router.location = createLocation('/test_link'); - jest.spyOn(document, 'getElementById').mockImplementation(() => { + vi.spyOn(document, 'getElementById').mockImplementation(() => { return document.createElement('div'); }); - const clearIntervalSpy = jest.spyOn(window, 'clearInterval'); + const clearIntervalSpy = vi.spyOn(window, 'clearInterval'); render(, { wrapper: Wrapper }); diff --git a/src/setupTests.ts b/src/setupTests.ts index 3bc05dc6..02ac9c7c 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -33,3 +33,7 @@ vi.mock('single-spa', () => ({ triggerAppChange: vi.fn(), NOT_LOADED: 'NOT_LOADED', })); + +// Recreate jest behaviour by mocking with __mocks__ by mocking globally here +vi.mock('axios'); +vi.mock('react-i18next'); diff --git a/src/state/actions/scigateway.actions.test.tsx b/src/state/actions/scigateway.actions.test.tsx index d9d10114..0cbb59bb 100644 --- a/src/state/actions/scigateway.actions.test.tsx +++ b/src/state/actions/scigateway.actions.test.tsx @@ -474,7 +474,7 @@ describe('scigateway actions', () => { }, }; - document.addEventListener = jest.fn( + document.addEventListener = vi.fn( (id: string, inputHandler: (event: Event) => void) => { inputHandler(new CustomEvent('test', { detail: registerRouteAction })); } @@ -552,7 +552,7 @@ describe('scigateway actions', () => { }, }); - const eventListenerSpy = jest.spyOn(document, 'addEventListener'); + const eventListenerSpy = vi.spyOn(document, 'addEventListener'); await asyncAction(dispatch, getState); @@ -697,7 +697,7 @@ describe('scigateway actions', () => { }) ); - jest.spyOn(window.localStorage.__proto__, 'getItem'); + vi.spyOn(window.localStorage.__proto__, 'getItem'); window.localStorage.__proto__.getItem = jest .fn() .mockImplementation((name) => (name === 'darkMode' ? 'true' : 'false')); @@ -728,7 +728,7 @@ describe('scigateway actions', () => { }) ); - jest.spyOn(window.localStorage.__proto__, 'getItem'); + vi.spyOn(window.localStorage.__proto__, 'getItem'); window.localStorage.__proto__.getItem = jest .fn() .mockImplementation((name) => @@ -756,7 +756,7 @@ describe('scigateway actions', () => { (mockAxios.get as jest.Mock).mockImplementationOnce(() => Promise.reject({}) ); - log.error = jest.fn(); + log.error = vi.fn(); const asyncAction = configureSite(); const actions: Action[] = []; @@ -777,7 +777,7 @@ describe('scigateway actions', () => { data: 1, }) ); - log.error = jest.fn(); + log.error = vi.fn(); const asyncAction = configureSite(); const actions: Action[] = []; @@ -796,7 +796,7 @@ describe('scigateway actions', () => { (mockAxios.get as jest.Mock).mockImplementationOnce(() => Promise.reject({}) ); - log.error = jest.fn(); + log.error = vi.fn(); const path = 'non/existent/path'; const asyncAction = loadStrings(path); diff --git a/src/state/middleware/scigateway.middleware.test.tsx b/src/state/middleware/scigateway.middleware.test.tsx index 623b09ea..0d305563 100644 --- a/src/state/middleware/scigateway.middleware.test.tsx +++ b/src/state/middleware/scigateway.middleware.test.tsx @@ -32,7 +32,7 @@ import { thunk } from 'redux-thunk'; import { autoLoginAuthorised } from '../actions/scigateway.actions'; import * as singleSpa from 'single-spa'; -jest.mock('single-spa'); +vi.mock('single-spa'); describe('scigateway middleware', () => { let events: CustomEvent[] = []; @@ -112,7 +112,7 @@ describe('scigateway middleware', () => { return true; }; - document.addEventListener = jest.fn( + document.addEventListener = vi.fn( (id: string, inputHandler: (event: Event) => void) => { handler = inputHandler; } @@ -120,13 +120,13 @@ describe('scigateway middleware', () => { store = mockStore(getState()); - Storage.prototype.getItem = jest.fn(() => 'false'); + Storage.prototype.getItem = vi.fn(() => 'false'); }); describe('autoLoginMiddleware', () => { let autoLogin: jest.Mock; beforeEach(() => { - autoLogin = jest.fn(() => Promise.resolve()); + autoLogin = vi.fn(() => Promise.resolve()); store = mockStore({ ...getState(), scigateway: { @@ -173,9 +173,9 @@ describe('scigateway middleware', () => { }); it('sends an error notification if autoLogin fails', async () => { - log.error = jest.fn(); + log.error = vi.fn(); - autoLogin = jest.fn(() => Promise.reject()); + autoLogin = vi.fn(() => Promise.reject()); store = mockStore({ ...getState(), scigateway: { @@ -374,8 +374,8 @@ describe('scigateway middleware', () => { location: createLocation('/'), }, }); - Storage.prototype.getItem = jest.fn().mockReturnValueOnce(undefined); - window.matchMedia = jest.fn().mockReturnValueOnce({ matches: true }); + Storage.prototype.getItem = vi.fn().mockReturnValueOnce(undefined); + window.matchMedia = vi.fn().mockReturnValueOnce({ matches: true }); const theme = buildTheme(true, false, '#654321'); const sendThemeOptionsAction = { type: SendThemeOptionsType, @@ -636,7 +636,7 @@ describe('scigateway middleware', () => { }); it('should listen for notification events and create toast for error', () => { - toastr.error = jest.fn(); + toastr.error = vi.fn(); listenToPlugins(store.dispatch, getState); const notificationAction = { @@ -656,7 +656,7 @@ describe('scigateway middleware', () => { }); it('should listen for notification events and create toast for warning', () => { - toastr.warning = jest.fn(); + toastr.warning = vi.fn(); listenToPlugins(store.dispatch, getState); const notificationAction = { @@ -675,7 +675,7 @@ describe('scigateway middleware', () => { }); it('should listen for notification events and create toast for information', () => { - toastr.info = jest.fn(); + toastr.info = vi.fn(); listenToPlugins(store.dispatch, getState); const notificationAction = { @@ -694,7 +694,7 @@ describe('scigateway middleware', () => { }); it('should listen for notification events and log error for invalid severity', () => { - log.error = jest.fn(); + log.error = vi.fn(); listenToPlugins(store.dispatch, getState); const notificationAction = { @@ -715,7 +715,7 @@ describe('scigateway middleware', () => { }); it('should broadcast requestpluginrerender action but ignore it itself', () => { - log.warn = jest.fn(); + log.warn = vi.fn(); const mockLog = (log.warn as jest.Mock).mock; listenToPlugins(store.dispatch, getState); @@ -732,7 +732,7 @@ describe('scigateway middleware', () => { }); it('should ignore BroadcastSignOut ', () => { - log.warn = jest.fn(); + log.warn = vi.fn(); const mockLog = (log.warn as jest.Mock).mock; listenToPlugins(store.dispatch, getState); @@ -743,7 +743,7 @@ describe('scigateway middleware', () => { }); it('should listen for events and not fire unrecognised action', () => { - log.warn = jest.fn(); + log.warn = vi.fn(); listenToPlugins(store.dispatch, getState); handler(new CustomEvent('test', { detail: action })); @@ -759,7 +759,7 @@ describe('scigateway middleware', () => { }); it('should not fire actions for events without detail', () => { - log.error = jest.fn(); + log.error = vi.fn(); listenToPlugins(store.dispatch, getState); @@ -776,7 +776,7 @@ describe('scigateway middleware', () => { }); it('should not fire actions for events without type on detail', () => { - log.error = jest.fn(); + log.error = vi.fn(); listenToPlugins(store.dispatch, getState); diff --git a/src/state/reducers/scigateway.reducer.test.tsx b/src/state/reducers/scigateway.reducer.test.tsx index 06670ced..d44a972a 100644 --- a/src/state/reducers/scigateway.reducer.test.tsx +++ b/src/state/reducers/scigateway.reducer.test.tsx @@ -129,7 +129,7 @@ describe('scigateway reducer', () => { }); it('should not add steps when a duplicate target property is found', () => { - log.error = jest.fn(); + log.error = vi.fn(); state.helpSteps = []; const steps = [ @@ -547,7 +547,7 @@ describe('scigateway reducer', () => { }); it('should log error and not register plugin with duplicate route in State', () => { - log.error = jest.fn(); + log.error = vi.fn(); const duplicatePayload = { ...basePayload, displayName: 'Duplicate Route', @@ -600,7 +600,7 @@ describe('scigateway reducer', () => { it('should log an error if an initialiseAnalytics message is sent with no analytics config', () => { delete state.analytics; - log.error = jest.fn(); + log.error = vi.fn(); const updatedState = ScigatewayReducer(state, initialiseAnalytics()); expect(updatedState.analytics).toBeUndefined(); diff --git a/src/tour/tour.component.test.tsx b/src/tour/tour.component.test.tsx index 19bd1c20..d83c87d9 100644 --- a/src/tour/tour.component.test.tsx +++ b/src/tour/tour.component.test.tsx @@ -13,8 +13,8 @@ import TestAuthProvider from '../authentication/testAuthProvider'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -jest.mock('popper.js', () => { - const PopperJS = jest.requireActual('popper.js'); +vi.mock('popper.js', async () => { + const PopperJS = await vi.requireActual('popper.js'); return class { public static placements = PopperJS.placements; @@ -84,7 +84,7 @@ describe('Tour component', () => { }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('can navigate between tutorial steps', async () => { @@ -145,9 +145,9 @@ describe('Tour component', () => { content: 'Plugin link test', }, ]; - jest.useFakeTimers(); + vi.useFakeTimers(); const user = userEvent.setup({ - advanceTimers: jest.advanceTimersByTime, + advanceTimers: vi.advanceTimersByTime, }); render( @@ -162,7 +162,7 @@ describe('Tour component', () => { await user.click(screen.getByLabelText('Next')); act(() => { - jest.runAllTimers(); + vi.runAllTimers(); }); expect(testStore.getActions().length).toEqual(1); diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 00000000..cb41eaa8 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "esnext", "DOM", "DOM.Iterable", + ], + "module": "ESNext", + "skipLibCheck": true, + + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + + /* Bundler mode */ + "moduleResolution": "node", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + }, + "include": [ + "src" + ] +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..303bffc9 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.base.json", + // Exclude test files from build + "exclude": ["**/?*test.*", "src/setupTests.ts"], +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 53afed36..11310aa2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,35 +1,6 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "esnext", "DOM", "DOM.Iterable", - ], - "module": "ESNext", - "skipLibCheck": true, - - "allowJs": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - - /* Bundler mode */ - "moduleResolution": "node", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "forceConsistentCasingInFileNames": true, - }, - "include": [ - "src" - ], - "exclude": [ - "**/?*test.*", - ] -} +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "types": ["vitest/globals", "vitest/jsdom"], + }, +}