diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 51% rename from .eslintrc.js rename to .eslintrc.cjs index ebbc32ef..0f319df3 100644 --- a/.eslintrc.js +++ b/.eslintrc.cjs @@ -1,23 +1,39 @@ module.exports = { parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, env: { browser: true, - jest: true, es6: true, node: true, }, + settings: { + react: { + version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use + }, + }, + plugins: [ + 'react', + 'react-hooks', + '@typescript-eslint', + 'cypress', + 'prettier', + ], extends: [ - 'react-app', + 'eslint:recommended', 'plugin:react/recommended', + 'plugin:react-hooks/recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/stylistic', - 'prettier', 'plugin:cypress/recommended', + 'prettier', ], - plugins: ['prettier', 'cypress'], rules: { - 'react/jsx-filename-extension': 'off', - 'jsx-a11y/click-events-have-key-events': 'off', + 'react/react-in-jsx-scope': 'off', 'prettier/prettier': [ 'error', { @@ -26,34 +42,25 @@ module.exports = { endOfLine: 'auto', }, ], - // disable for all files - this means we can have plain JS files not causing errors - '@typescript-eslint/explicit-function-return-type': 'off', + // Emulate typescript style for unused variables, see + // https://typescript-eslint.io/rules/no-unused-vars/ + '@typescript-eslint/no-unused-vars': [ + 'error', + { + args: 'all', + argsIgnorePattern: '^_', + caughtErrors: 'all', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], }, overrides: [ { - // and enable again specifically for TS files - files: ['*.ts', '*.tsx'], - rules: { - '@typescript-eslint/explicit-function-return-type': [ - 'error', - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - }, - ], - }, + files: ['**/?*test.*'], + extends: ['plugin:testing-library/react'], }, ], - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - allowImportExportEverywhere: true, - sourceType: 'module', - }, - settings: { - react: { - version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use - }, - }, }; diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index db19ee0f..cdd09ecd 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -25,7 +25,7 @@ jobs: sudo apt-get install libgconf-2-4 yarn --immutable - name: Run linting - run: yarn lint:js + run: yarn lint - name: Run unit tests run: yarn test - name: Upload unit test coverage diff --git a/.gitignore b/.gitignore index 343d0d06..51a7eb56 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ /coverage # production -/build +dist # misc .DS_Store diff --git a/Dockerfile b/Dockerfile index 04070e53..25f30779 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ FROM httpd:2.4.62-alpine3.20@sha256:66c49302c02430619abb84240a438bcfc08301566100 WORKDIR /usr/local/apache2/htdocs # Put the output of the build into an apache server -COPY --from=builder /scigateway-build/build/. . +COPY --from=builder /scigateway-build/dist/. . RUN set -eux; \ \ diff --git a/README.md b/README.md index b861847e..146ba7ab 100644 --- a/README.md +++ b/README.md @@ -8,47 +8,38 @@ The SciGateway application offers features such as authentication and authorisat ## Code details -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +This project uses [Vite](https://vitejs.dev/). #### Available Scripts In the project directory, you can run: -#### `yarn start` +### `yarn dev` -Runs the app in the development mode.
+Runs the `dev` script, which runs the app in development mode. Open [http://localhost:3000](http://localhost:3000) to view it in the browser. -The page will reload if you make edits.
-You will also see any lint errors in the console. +### `yarn test` -#### `yarn test` +Runs unit tests -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +### `yarn e2e` -#### `yarn build` +Runs e2e tests -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. +### `yarn lint` -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -#### `yarn eject` +Lints all code under /src -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** +### `yarn build` -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. +Builds the app for production to the `dist` folder. -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. +The build is minified and the filenames include the hashes. +Your app is ready to be deployed! -### Learn More +See the section about [building for production](https://vitejs.dev/guide/build.html) for more information. -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). +### `yarn preview` -To learn React, check out the [React documentation](https://reactjs.org/). +Deploys a static version of the build from the `dist` directory to port 5001. Use `yarn preview:build` to build and preview it in SciGateway. diff --git a/__mocks__/axios.ts b/__mocks__/axios.ts new file mode 100644 index 00000000..23d64990 --- /dev/null +++ b/__mocks__/axios.ts @@ -0,0 +1,21 @@ +const requests = { + get: vi.fn((path) => { + if (path === '/settings.json') { + return Promise.resolve({ + data: { + 'auth-provider': 'jwt', + 'ui-strings': '/res/default.json', + plugins: [], + 'help-tour-steps': [], + }, + }); + } else { + return 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/cypress.config.ts b/cypress.config.ts index 13cd5717..675ad99a 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'cypress' +import { defineConfig } from 'cypress'; export default defineConfig({ chromeWebSecurity: false, @@ -8,11 +8,6 @@ export default defineConfig({ openMode: 0, }, e2e: { - setupNodeEvents(on, config) { - on('task', { - failed: require('cypress-failed-log/src/failed')(), - }) - }, baseUrl: 'http://127.0.0.1:3000', }, -}) +}); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index fd36e15a..37a498fb 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -18,5 +18,3 @@ import './commands'; // Alternatively you can use CommonJS syntax: // require('./commands') - -require('cypress-failed-log'); diff --git a/index.html b/index.html new file mode 100644 index 00000000..c31fc76f --- /dev/null +++ b/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + SciGateway + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/micro-frontend-tools/serve-plugins.js b/micro-frontend-tools/serve-plugins.js index 7e142529..3333f1c1 100644 --- a/micro-frontend-tools/serve-plugins.js +++ b/micro-frontend-tools/serve-plugins.js @@ -1,8 +1,13 @@ -var fs = require('fs'); -const path = require('path'); -var exec = require('child_process').exec; +import fs from 'fs'; +import path from 'path'; +import child_process from 'child_process'; -const settingsFilePath = path.join(__dirname, '/dev-plugin-settings.json'); +var exec = child_process.exec; + +const settingsFilePath = path.join( + import.meta.dirname, + '/dev-plugin-settings.json' +); function checkForSettingsFile() { if (!fs.existsSync(settingsFilePath)) { diff --git a/package.json b/package.json index 4b167c7a..a0be0c6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "scigateway", "version": "3.0.0", + "type": "module", "private": true, "resolutions": { "@types/react": "18.0.33", @@ -20,11 +21,12 @@ "@types/react-redux-toastr": "7.6.2", "@types/react-router-dom": "5.3.3", "@types/redux-logger": "3.0.8", + "@vitejs/plugin-react": "^4.3.4", "axios": "1.7.4", + "browserslist": "4.24.2", + "browserslist-to-esbuild": "2.1.1", "connected-react-router": "6.9.3", "cookie-parser": "1.4.5", - "custom-event-polyfill": "1.0.7", - "cypress-failed-log": "2.10.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-cypress": "2.15.1", "eslint-plugin-prettier": "5.1.3", @@ -40,36 +42,35 @@ "prop-types": "15.8.1", "query-string": "7.1.1", "react": "18.2.0", - "react-app-polyfill": "3.0.0", "react-dom": "18.2.0", "react-i18next": "14.0.1", "react-joyride": "2.7.2", "react-redux": "8.1.2", - "react-redux-toastr": "7.6.8", + "react-redux-toastr": "7.6.13", "react-router-dom": "5.3.0", - "react-scripts": "5.0.0", "redux": "4.2.1", "redux-logger": "3.0.6", "redux-thunk": "3.1.0", "single-spa": "5.9.4", "typeface-roboto": "1.1.13", - "typescript": "5.3.3" + "typescript": "5.3.3", + "vite": "^5.4.11" }, "scripts": { - "lint:js": "eslint --max-warnings=0 --ext=tsx --ext=ts --ext=js --ext=jsx --fix ./src", + "dev": "cross-env concurrently \"yarn serve:plugins\" \"node server/auth-server.js\" \"vite --open\"", + "build": "tsc --project tsconfig.build.json && vite build", + "preview": "vite preview", + "preview:build": "yarn build && yarn preview", + "test": "vitest --coverage", + "lint": "eslint --max-warnings=0 --ext=tsx --ext=ts --ext=js --ext=jsx --fix ./src", "lint:cypress": "eslint --ext=tsx --ext=js --ext=jsx --fix ./cypress", "serve:plugins": "node micro-frontend-tools/serve-plugins.js", - "start": "cross-env concurrently \"yarn serve:plugins\" \"node server/auth-server.js\" \"react-scripts start\"", - "build": "react-scripts build", - "build:e2e": "cross-env GENERATE_SOURCEMAP=false react-scripts build", - "test": "react-scripts test --env=jsdom --coverage --watchAll=false", - "test:watch": "react-scripts test --env=jsdom --watch", + "build:e2e": "cross-env GENERATE_SOURCEMAP=false yarn build", "e2e:serve": "yarn build:e2e && concurrently \"node server/auth-server.js e2e\" \"node ./server/e2e-test-server.js\"", "e2e:interactive": "start-server-and-test e2e:serve http://localhost:3000 cy:open", "e2e": "start-server-and-test e2e:serve http://localhost:3000 cy:run", "cy:open": "cypress open", "cy:run": "cypress run", - "eject": "react-scripts eject", "postinstall": "husky install; yarn copy-cdn-fallbacks", "copy-cdn-fallbacks": "node -e \"const fs = require('fs'); fs.copyFile('node_modules/react/umd/react.production.min.js', 'public/react.production.min.js', (err) => { if (err) throw err;}); fs.copyFile('node_modules/react-dom/umd/react-dom.production.min.js', 'public/react-dom.production.min.js', (err) => { if (err) throw err;});\"" }, @@ -83,23 +84,9 @@ "prettier --config .prettierrc --write" ] }, - "eslintConfig": { - "extends": "react-app" - }, - "jest": { - "collectCoverageFrom": [ - "src/**/*.{tsx,js,jsx,ts}", - "!src/index.tsx", - "!src/serviceWorker.ts", - "!src/setupTests.ts", - "!src/state/reducers/App.reducer.tsx", - "!src/state/actions/loadMicroFrontends.tsx" - ] - }, "browserslist": [ ">0.2%", "not dead", - "not ie <= 10", "not op_mini all" ], "devDependencies": { @@ -115,17 +102,22 @@ "@types/redux-mock-store": "1.0.2", "@typescript-eslint/eslint-plugin": "7.0.2", "@typescript-eslint/parser": "7.0.2", + "@vitest/coverage-v8": "2.1.6", "axios-mock-adapter": "1.22.0", "concurrently": "8.2.0", "cors": "2.8.5", "cross-env": "7.0.3", "cypress": "13.6.4", "eslint": "8.56.0", - "eslint-config-react-app": "7.0.0", + "eslint-plugin-react": "7.37.2", + "eslint-plugin-react-hooks": "4.6.0", + "eslint-plugin-testing-library": "6.2.0", + "jsdom": "24.0.0", "lint-staged": "15.2.0", "redux-mock-store": "1.5.4", "serve": "14.2.0", "start-server-and-test": "2.0.0", + "vitest": "2.1.6", "wait-on": "7.2.0" }, "packageManager": "yarn@4.1.0" diff --git a/public/index.html b/public/index.html deleted file mode 100644 index fdecfc10..00000000 --- a/public/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - SciGateway - - - - - - - - -
- - - - \ No newline at end of file diff --git a/server/auth-server.js b/server/auth-server.js index 5a59f88e..8c6bb7a6 100644 --- a/server/auth-server.js +++ b/server/auth-server.js @@ -1,12 +1,12 @@ -const express = require('express'); -const axios = require('axios'); -const jwt = require('jsonwebtoken'); -const qs = require('query-string'); -const cookieParser = require('cookie-parser'); -const https = require('https'); -const fs = require('fs'); -const waitOn = require('wait-on'); -const cors = require('cors'); +import express from 'express'; +import axios from 'axios'; +import jwt from 'jsonwebtoken'; +import qs from 'query-string'; +import cookieParser from 'cookie-parser'; +import https from 'https'; +import fs from 'fs'; +import waitOn from 'wait-on'; +import cors from 'cors'; const app = express(); const port = 8000; diff --git a/server/e2e-test-server.js b/server/e2e-test-server.js index 0263f4ba..41c60555 100644 --- a/server/e2e-test-server.js +++ b/server/e2e-test-server.js @@ -1,24 +1,26 @@ -var express = require('express'); -var path = require('path'); -var serveStatic = require('serve-static'); -var axios = require('axios'); +import express from 'express'; +import path from 'path'; +import serveStatic from 'serve-static'; +import axios from 'axios'; var app = express(); -app.get('/settings.json', function(req, res) { - res.sendFile(path.join(__dirname, 'e2e-settings.json')); +app.get('/settings.json', function (req, res) { + res.sendFile(path.resolve('./server/e2e-settings.json')); }); -app.get('/plugins/*', function(req, res) { - res.sendFile(path.join(__dirname, req.originalUrl.replace('/plugins/', ''))); +app.get('/plugins/*', function (req, res) { + res.sendFile( + path.resolve(`./server/${req.originalUrl.replace('/plugins/', '')}`) + ); }); app.use( express.json(), - serveStatic(path.resolve('./build'), { index: ['index.html', 'index.htm'] }) + serveStatic(path.resolve('./dist'), { index: ['index.html', 'index.htm'] }) ); -app.post('/api/*', function(req, res) { +app.post('/api/*', function (req, res) { axios .post('http://127.0.0.1:8000' + req.url, req.body) .then(apiRes => { @@ -29,8 +31,8 @@ app.post('/api/*', function(req, res) { }); }); -app.get('/*', function(req, res) { - res.sendFile(path.resolve('./build/index.html')); +app.get('/*', function (req, res) { + res.sendFile(path.resolve('./dist/index.html')); }); app.listen(3000); diff --git a/src/App.css b/src/App.css index b41d297c..cac98ed2 100644 --- a/src/App.css +++ b/src/App.css @@ -1,33 +1,34 @@ -.App { - text-align: center; +body { + margin: 0; + padding: 0; + background-color: #212121; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -.App-logo { - animation: App-logo-spin infinite 20s linear; - height: 40vmin; - pointer-events: none; +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; +.top-center { + top: 70px !important; } -.App-link { - color: #61dafb; +/* Need to modify to black as the default white isn't good enough contrast */ +.redux-toastr :is(.toastr.rrt-warning, .toastr.rrt-error, .toastr.rrt-info, .toastr.rrt-success) { + color: #000000; } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } +.redux-toastr .toastr .rrt-left-container .toastr-icon { + fill: #000000; +} + +@keyframes rotate { to { - transform: rotate(360deg); + transform: rotateZ(360deg); } } diff --git a/src/App.test.tsx b/src/App.test.tsx index 1dbdc25e..03d73d6e 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,42 +1,71 @@ import { useMediaQuery } from '@mui/material'; import { act, fireEvent, render, screen } from '@testing-library/react'; import axios from 'axios'; -import React from 'react'; import { createRoot } from 'react-dom/client'; import App, { AppSansHoc } from './App'; -import { flushPromises } from './setupTests'; import { RegisterRouteType } from './state/scigateway.types'; +import { flushPromises } from './testUtils'; -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, has to be hoisted in order to be run before any imports +vi.hoisted(() => { + const testToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QifQ.hNQI_r8BATy1LyXPr6Zuo9X_V0kSED8ngcqQ6G-WV5w'; + vi.spyOn(window.localStorage.__proto__, 'getItem').mockImplementation( + (name) => (name === 'scigateway:token' ? testToken : null) + ); +}); -// 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) => { - return name === 'scigateway:token' ? testToken : null; +/* Have to remock to replace the auth-provider just for this file. We used to be able to use + expect.getState().testPath?.includes('App.test') inside the __mocks__ folder, but this is undefined + until the tests in this file are actually executed since migrating from Jest to Vitest */ +vi.mock('axios', async () => { + return { + default: { + get: vi.fn((path) => { + if (path === '/settings.json') { + return Promise.resolve({ + data: { + // Set provider to icat as that supports maintenance states needed for App.test.tsx + 'auth-provider': 'icat', + 'ui-strings': '/res/default.json', + plugins: [], + 'help-tour-steps': [], + }, + }); + } else { + return Promise.resolve({ + data: {}, + }); + } + }), + post: vi.fn(() => Promise.resolve({ data: {} })), + }, + }; }); describe('App', () => { beforeEach(() => { - jest.mocked(useMediaQuery).mockReturnValue(true); + vi.mocked(useMediaQuery).mockReturnValue(true); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('renders without crashing', () => { const div = document.createElement('div'); const root = createRoot(div); + // eslint-disable-next-line testing-library/no-unnecessary-act act(() => { root.render(); }); @@ -46,27 +75,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(() => + vi.mocked(axios.post).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, @@ -88,13 +117,14 @@ describe('App', () => { }); // go to plugin page - await fireEvent.click(screen.getByRole('link', { name: 'Test plugin' })); + fireEvent.click(screen.getByRole('link', { name: 'Test plugin' })); + // eslint-disable-next-line testing-library/no-node-access expect(document.getElementById('test_plugin')).toBeInTheDocument(); expect(screen.queryByText('Maintenance')).not.toBeInTheDocument(); - (axios.get as jest.Mock).mockImplementation(() => + vi.mocked(axios.get).mockImplementation(() => Promise.resolve({ data: { show: true, @@ -104,7 +134,7 @@ describe('App', () => { ); act(() => { - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); }); await act(async () => { @@ -117,7 +147,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(() => + vi.mocked(axios.get).mockImplementation(() => Promise.resolve({ data: { show: false, @@ -126,7 +156,7 @@ describe('App', () => { }) ); - jest.runOnlyPendingTimers(); + vi.runOnlyPendingTimers(); await act(async () => { await flushPromises(); diff --git a/src/App.tsx b/src/App.tsx index d8e6cdbc..0b40903a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,10 +2,13 @@ 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'; 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, @@ -16,12 +19,10 @@ import ScigatewayMiddleware, { } from './state/middleware/scigateway.middleware'; import AppReducer from './state/reducers/App.reducer'; import { StateType } from './state/state.types'; -import './index.css'; import { ConnectedThemeProvider } from './theming'; +// This order needed for the App.css to apply to toasts correctly import ReduxToastr from 'react-redux-toastr'; -import PageContainer from './pageContainer.component'; -import { Preloader } from './preloader/preloader.component'; -import { WithTranslation, withTranslation } from 'react-i18next'; +import './App.css'; const history = createBrowserHistory(); @@ -31,7 +32,7 @@ const middleware = [ ScigatewayMiddleware, autoLoginMiddleware, ]; -if (process.env.NODE_ENV === `development`) { +if (import.meta.env.DEV) { const logger = createLogger({ collapsed: true }); middleware.push(logger); log.setDefaultLevel(log.levels.DEBUG); diff --git a/src/__mocks__/axios.ts b/src/__mocks__/axios.ts deleted file mode 100644 index bfbffc10..00000000 --- a/src/__mocks__/axios.ts +++ /dev/null @@ -1,26 +0,0 @@ -// TODO: move __mocks__ folder back to root once facebook/create-react-app#7539 is fixed - -const requests = { - get: jest.fn((path) => { - if (path === '/settings.json') { - return Promise.resolve({ - data: { - // Set provider to icat as that supports maintenance states needed for App.test.tsx - 'auth-provider': expect.getState().testPath?.includes('App.test') - ? 'icat' - : 'jwt', - 'ui-strings': '/res/default.json', - plugins: [], - 'help-tour-steps': [], - }, - }); - } else { - return Promise.resolve({ - data: {}, - }); - } - }), - post: jest.fn(() => Promise.resolve({ data: {} })), -}; - -export default requests; diff --git a/src/__snapshots__/example.component.test.tsx.snap b/src/__snapshots__/example.component.test.tsx.snap index d74f7669..3e284f6c 100644 --- a/src/__snapshots__/example.component.test.tsx.snap +++ b/src/__snapshots__/example.component.test.tsx.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Example component renders correctly 1`] = ` +exports[`Example component > renders correctly 1`] = `
diff --git a/src/__snapshots__/pageContainer.test.tsx.snap b/src/__snapshots__/pageContainer.test.tsx.snap index 52839bc5..b82d30df 100644 --- a/src/__snapshots__/pageContainer.test.tsx.snap +++ b/src/__snapshots__/pageContainer.test.tsx.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`PageContainer - Tests renders correctly 1`] = ` +exports[`PageContainer - Tests > renders correctly 1`] = `
+
- + + + +
+
+ +

Data discovery @@ -160,7 +218,7 @@ exports[`PageContainer - Tests renders correctly 1`] = `

for @@ -172,7 +230,7 @@ exports[`PageContainer - Tests renders correctly 1`] = `

+
+
+
+
+
`; diff --git a/src/accessibilityPage/__snapshots__/accessibilityPage.component.test.tsx.snap b/src/accessibilityPage/__snapshots__/accessibilityPage.component.test.tsx.snap index 19af9770..ad36876e 100644 --- a/src/accessibilityPage/__snapshots__/accessibilityPage.component.test.tsx.snap +++ b/src/accessibilityPage/__snapshots__/accessibilityPage.component.test.tsx.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Accessibility page component should render correctly and display contact us component 1`] = ` +exports[`Accessibility page component > should render correctly and display contact us component 1`] = `
{ } afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should render maintenance page correctly', () => { diff --git a/src/adminPage/adminPage.component.tsx b/src/adminPage/adminPage.component.tsx index 5739de2e..1436f047 100644 --- a/src/adminPage/adminPage.component.tsx +++ b/src/adminPage/adminPage.component.tsx @@ -59,7 +59,7 @@ const AdminPage = (props: AdminPageProps): ReactElement => { location.pathname.startsWith(adminRoutes[key]) ) ?? (props.adminPageDefaultTab && - adminRoutes.hasOwnProperty(props.adminPageDefaultTab) + Object.hasOwn(adminRoutes, props.adminPageDefaultTab) ? props.adminPageDefaultTab : 'maintenance') ); @@ -84,7 +84,7 @@ const AdminPage = (props: AdminPageProps): ReactElement => { textColor="secondary" indicatorColor="secondary" value={tabValue} - onChange={(event, newValue) => { + onChange={(_event, newValue) => { setTabValue(newValue); }} > diff --git a/src/adminPage/maintenancePage.component.test.tsx b/src/adminPage/maintenancePage.component.test.tsx index dca164e1..fc79e9b8 100644 --- a/src/adminPage/maintenancePage.component.test.tsx +++ b/src/adminPage/maintenancePage.component.test.tsx @@ -80,14 +80,14 @@ describe('maintenance page component', () => { await waitFor(() => { expect(store.getActions().length).toEqual(1); - expect(store.getActions()[0]).toEqual( - loadScheduledMaintenanceState({ - show: true, - message: 'test', - severity: 'information', - }) - ); }); + expect(store.getActions()[0]).toEqual( + loadScheduledMaintenanceState({ + show: true, + message: 'test', + severity: 'information', + }) + ); }); it('setMaintenanceState action should be sent when the setMaintenanceState function is called', async () => { diff --git a/src/authentication/baseAuthProvider.tsx b/src/authentication/baseAuthProvider.tsx index fc9cfe39..d94ecc6f 100644 --- a/src/authentication/baseAuthProvider.tsx +++ b/src/authentication/baseAuthProvider.tsx @@ -37,7 +37,7 @@ export default abstract class BaseAuthProvider implements AuthProvider { user.isAdmin = tokenObject.userIsAdmin; return user; } - } catch (TypeError) { + } catch (_err) { // not a valid JWT, token has likely been tampered with in some way (or we are running tests) console.error('Invalid token: failed to authenticate'); } diff --git a/src/authentication/githubAuthProvider.test.tsx b/src/authentication/githubAuthProvider.test.tsx index 1a3df830..1fdf2005 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(() => + vi.mocked(mockAxios.post).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(() => + vi.mocked(mockAxios.post).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(() => + vi.mocked(mockAxios.post).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(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve({ data: { username: 'test_user', diff --git a/src/authentication/githubAuthProvider.tsx b/src/authentication/githubAuthProvider.tsx index 9d5ed76a..61beca11 100644 --- a/src/authentication/githubAuthProvider.tsx +++ b/src/authentication/githubAuthProvider.tsx @@ -9,7 +9,7 @@ export default class GithubAuthProvider extends BaseAuthProvider { 'https://github.com/login/oauth/authorize?client_id=9fb0c571fd7b71e383b4'; } - public logIn(username: string, password: string): Promise { + public logIn(_username: string, password: string): Promise { const params = qs.parse(password); // remove existing credentials so they can be refreshed diff --git a/src/authentication/icatAuthProvider.test.tsx b/src/authentication/icatAuthProvider.test.tsx index 08eeeb07..0b4bc6dc 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; @@ -11,7 +11,7 @@ describe('ICAT auth provider', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QifQ.hNQI_r8BATy1LyXPr6Zuo9X_V0kSED8ngcqQ6G-WV5w'; beforeEach(() => { - window.localStorage.__proto__.getItem = jest + window.localStorage.__proto__.getItem = vi .fn() .mockImplementation((name) => { if (name === 'scigateway:token') { @@ -22,20 +22,20 @@ 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', 'http://localhost:8000', true ); - (parseJwt as jest.Mock).mockImplementation( + vi.mocked(parseJwt).mockImplementation( (token) => `{"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 () => { @@ -59,9 +59,7 @@ describe('ICAT auth provider', () => { expect(localStorage.removeItem).toBeCalledWith('scigateway:token'); expect(icatAuthProvider.isLoggedIn()).toBeFalsy(); expect(document.dispatchEvent).toHaveBeenCalled(); - expect( - (document.dispatchEvent as jest.Mock).mock.calls[0][0].detail - ).toEqual({ + expect(vi.mocked(document.dispatchEvent).mock.calls[0][0].detail).toEqual({ type: BroadcastSignOutType, }); }); @@ -71,12 +69,12 @@ describe('ICAT auth provider', () => { }); it('should successfully log in if user is already logged in via autoLogin', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve({ data: testToken, }) ); - window.localStorage.__proto__.getItem = jest + window.localStorage.__proto__.getItem = vi .fn() .mockImplementation((name) => { if (name === 'scigateway:token') { @@ -92,9 +90,7 @@ describe('ICAT auth provider', () => { // should send sign out action for autologin logout expect(document.dispatchEvent).toHaveBeenCalled(); - expect( - (document.dispatchEvent as jest.Mock).mock.calls[0][0].detail - ).toEqual({ + expect(vi.mocked(document.dispatchEvent).mock.calls[0][0].detail).toEqual({ type: BroadcastSignOutType, }); @@ -109,7 +105,7 @@ describe('ICAT auth provider', () => { }); it('should call the api to authenticate', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve({ data: testToken, }) @@ -133,7 +129,7 @@ describe('ICAT auth provider', () => { }); it('should log the user out for an invalid login attempt', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -153,14 +149,14 @@ describe('ICAT auth provider', () => { }); it('should attempt to autologin via anon authenticator when initialised', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve({ data: testToken, }) ); // ensure token is null - window.localStorage.__proto__.getItem = jest.fn().mockReturnValue(null); + window.localStorage.__proto__.getItem = vi.fn().mockReturnValue(null); icatAuthProvider = new ICATAuthProvider( undefined, @@ -187,7 +183,7 @@ describe('ICAT auth provider', () => { }); it('should set autoLogin to false if autoLogin fails', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -196,7 +192,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, @@ -240,7 +236,7 @@ describe('ICAT auth provider', () => { }); it('should call api to verify token', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => Promise.resolve()); + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve()); await icatAuthProvider.verifyLogIn(); @@ -250,14 +246,14 @@ describe('ICAT auth provider', () => { }); it('should call refresh if the access token has expired', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.reject({ response: { status: 401, }, }) ); - const refreshSpy = jest + const refreshSpy = vi .spyOn(icatAuthProvider, 'refresh') .mockImplementationOnce(() => Promise.resolve()); @@ -267,7 +263,7 @@ describe('ICAT auth provider', () => { }); it('should update the token if the refresh method is successful', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve({ data: 'new-token', }) @@ -288,7 +284,7 @@ describe('ICAT auth provider', () => { }); it('should log the user out if the refresh token has expired', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -305,7 +301,7 @@ describe('ICAT auth provider', () => { }); it('should call api to fetch scheduled maintenance state', async () => { - (mockAxios.get as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.get).mockImplementation(() => Promise.resolve({ data: { show: false, @@ -321,7 +317,7 @@ describe('ICAT auth provider', () => { }); it('should log the user out if it fails to fetch scheduled maintenance state', async () => { - (mockAxios.get as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.get).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -338,7 +334,7 @@ describe('ICAT auth provider', () => { }); it('should call api to fetch maintenance state', async () => { - (mockAxios.get as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.get).mockImplementation(() => Promise.resolve({ data: { show: false, @@ -354,7 +350,7 @@ describe('ICAT auth provider', () => { }); it('should log the user out if it fails to fetch maintenance state', async () => { - (mockAxios.get as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.get).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -372,7 +368,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 +389,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, @@ -413,7 +409,7 @@ describe('ICAT auth provider', () => { it('should call api to set maintenance state', async () => { const maintenanceState = { show: true, message: 'test' }; - mockAxios.put = jest + mockAxios.put = vi .fn() .mockImplementation(() => Promise.resolve({ data: 'test' })); @@ -427,7 +423,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..234a82c8 100644 --- a/src/authentication/jwtAuthProvider.test.tsx +++ b/src/authentication/jwtAuthProvider.test.tsx @@ -7,20 +7,20 @@ describe('jwt auth provider', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJ1c2VySXNBZG1pbiI6ZmFsc2V9.PEuKaAD98doFTLyqcNFpsuv50AQR8ejrbDQ0pwazM7Q'; 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(); jwtAuthProvider = new JWTAuthProvider('http://localhost:8000'); }); afterEach(() => { - (mockAxios.post as jest.Mock).mockClear(); + vi.mocked(mockAxios.post).mockClear(); }); it('should load the token when built', () => { @@ -54,7 +54,7 @@ describe('jwt auth provider', () => { }); it('should call the api to authenticate', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve({ data: testToken, }) @@ -73,7 +73,7 @@ describe('jwt auth provider', () => { }); it('should log the user out for an invalid login attempt', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -93,7 +93,7 @@ describe('jwt auth provider', () => { }); it('should call api to verify token', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => Promise.resolve()); + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve()); await jwtAuthProvider.verifyLogIn(); @@ -103,7 +103,7 @@ describe('jwt auth provider', () => { }); it('should call refresh if the access token has expired', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -111,7 +111,7 @@ describe('jwt auth provider', () => { }) ); - const refreshSpy = jest + const refreshSpy = vi .spyOn(jwtAuthProvider, 'refresh') .mockImplementationOnce(() => Promise.resolve()); @@ -123,7 +123,7 @@ describe('jwt auth provider', () => { }); it('should update the token if the refresh method is successful', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.resolve({ data: 'new-token', }) @@ -144,7 +144,7 @@ describe('jwt auth provider', () => { }); it('should log the user out if the refresh token has expired', async () => { - (mockAxios.post as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.post).mockImplementation(() => Promise.reject({ response: { status: 401, diff --git a/src/authentication/ldapJWTAuthProvider.test.tsx b/src/authentication/ldapJWTAuthProvider.test.tsx index 20382800..c6d9ef90 100644 --- a/src/authentication/ldapJWTAuthProvider.test.tsx +++ b/src/authentication/ldapJWTAuthProvider.test.tsx @@ -9,7 +9,7 @@ describe('LDAP-JWT Auth provider', () => { }); it('should call api to fetch maintenance state', async () => { - (mockAxios.get as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.get).mockImplementation(() => Promise.resolve({ data: { show: false, @@ -25,7 +25,7 @@ describe('LDAP-JWT Auth provider', () => { }); it('should call api to fetch scheduled maintenance state', async () => { - (mockAxios.get as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.get).mockImplementation(() => Promise.resolve({ data: { show: false, @@ -42,7 +42,7 @@ describe('LDAP-JWT Auth provider', () => { }); it('should log the user out if it fails to fetch maintenance state', async () => { - (mockAxios.get as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.get).mockImplementation(() => Promise.reject({ response: { status: 401, @@ -58,7 +58,7 @@ describe('LDAP-JWT Auth provider', () => { }); it('should log the user out if it fails to fetch scheduled maintenance state', async () => { - (mockAxios.get as jest.Mock).mockImplementation(() => + vi.mocked(mockAxios.get).mockImplementation(() => Promise.reject({ response: { status: 401, diff --git a/src/authentication/nullAuthProvider.test.tsx b/src/authentication/nullAuthProvider.test.tsx index 6c0c7685..66e08fde 100644 --- a/src/authentication/nullAuthProvider.test.tsx +++ b/src/authentication/nullAuthProvider.test.tsx @@ -9,7 +9,7 @@ describe('null auth provider', () => { }); afterEach(() => { - (mockAxios.post as jest.Mock).mockClear(); + vi.mocked(mockAxios.post).mockClear(); }); it('should be always logged in, logIn() and logOut() should do nothing', () => { diff --git a/src/authentication/parseJwt.ts b/src/authentication/parseJwt.ts index 1fcff063..643309a2 100644 --- a/src/authentication/parseJwt.ts +++ b/src/authentication/parseJwt.ts @@ -5,7 +5,7 @@ const parseJwt = (token: string): string => { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const payload = decodeURIComponent( - atob(base64).replace(/(.)/g, function (m, p) { + atob(base64).replace(/(.)/g, function (_m, p) { const code = p.charCodeAt(0).toString(16).toUpperCase(); return '%' + ('00' + code).slice(-2); }) diff --git a/src/authentication/testAuthProvider.tsx b/src/authentication/testAuthProvider.tsx index 016ef89a..696fe468 100644 --- a/src/authentication/testAuthProvider.tsx +++ b/src/authentication/testAuthProvider.tsx @@ -63,13 +63,13 @@ export default class TestAuthProvider implements AuthProvider { } public setScheduledMaintenanceState( - scheduledMaintenanceState: ScheduledMaintenanceState + _scheduledMaintenanceState: ScheduledMaintenanceState ): Promise { return Promise.resolve('test'); } public setMaintenanceState( - maintenanceState: MaintenanceState + _maintenanceState: MaintenanceState ): Promise { return Promise.resolve('test'); } diff --git a/src/cookieConsent/__snapshots__/cookiesPage.component.test.tsx.snap b/src/cookieConsent/__snapshots__/cookiesPage.component.test.tsx.snap index 54fb2448..8e328184 100644 --- a/src/cookieConsent/__snapshots__/cookiesPage.component.test.tsx.snap +++ b/src/cookieConsent/__snapshots__/cookiesPage.component.test.tsx.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Cookies page component should render correctly 1`] = ` +exports[`Cookies page component > should render correctly 1`] = `
{ }); 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 }); @@ -84,16 +84,16 @@ describe('Cookie consent component', () => { await user.click(screen.getByRole('button', { name: 'accept-button' })); expect(Cookies.set).toHaveBeenCalled(); - const mockCookies = (Cookies.set as jest.Mock).mock; + const mockCookies = vi.mocked(Cookies.set).mock; const callArguments = mockCookies.calls[0]; expect(callArguments[0]).toEqual('cookie-consent'); expect(callArguments[1]).toEqual(JSON.stringify({ analytics: true })); }); 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 + Cookies.get = vi .fn() .mockImplementationOnce((name) => name === 'cookie-consent' ? JSON.stringify({ analytics: true }) : 'null' @@ -126,7 +126,7 @@ describe('Cookie consent component', () => { }); it('should set open to false if cookie-consent cookie is set', () => { - Cookies.get = jest + Cookies.get = vi .fn() .mockImplementation((name) => name === 'cookie-consent' ? JSON.stringify({ analytics: true }) : null diff --git a/src/cookieConsent/cookieConsent.component.tsx b/src/cookieConsent/cookieConsent.component.tsx index 83e9069a..ebf9ae33 100644 --- a/src/cookieConsent/cookieConsent.component.tsx +++ b/src/cookieConsent/cookieConsent.component.tsx @@ -81,7 +81,7 @@ export const CookieConsent = ( }, [props]); const handleAccept = ( - event: React.SyntheticEvent | React.MouseEvent + _event: React.SyntheticEvent | React.MouseEvent ): void => { Cookies.set('cookie-consent', JSON.stringify({ analytics: true }), { expires: 365, diff --git a/src/cookieConsent/cookiesPage.component.test.tsx b/src/cookieConsent/cookiesPage.component.test.tsx index 93151cd4..1cc6f97a 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); @@ -74,7 +74,7 @@ describe('Cookies page component', () => { ); expect(Cookies.set).toHaveBeenCalled(); - const mockCookies = (Cookies.set as jest.Mock).mock; + const mockCookies = vi.mocked(Cookies.set).mock; const callArguments = mockCookies.calls[0]; expect(callArguments[0]).toEqual('cookie-consent'); expect(callArguments[1]).toEqual(JSON.stringify({ analytics: true })); @@ -85,7 +85,7 @@ describe('Cookies page component', () => { it('should remove cookies when user revokes consent', async () => { const user = userEvent.setup(); - Cookies.get = jest + Cookies.get = vi .fn() .mockImplementationOnce((name) => name === 'cookie-consent' ? JSON.stringify({ analytics: true }) : null @@ -106,13 +106,13 @@ describe('Cookies page component', () => { ); expect(Cookies.set).toHaveBeenCalled(); - const mockCookiesSet = (Cookies.set as jest.Mock).mock; + const mockCookiesSet = vi.mocked(Cookies.set).mock; const setCallArguments = mockCookiesSet.calls[0]; expect(setCallArguments[0]).toEqual('cookie-consent'); expect(setCallArguments[1]).toEqual(JSON.stringify({ analytics: false })); expect(Cookies.remove).toHaveBeenCalledTimes(2); - const mockCookiesRemove = (Cookies.remove as jest.Mock).mock; + const mockCookiesRemove = vi.mocked(Cookies.remove).mock; expect(mockCookiesRemove.calls[0][0]).toEqual('_ga'); expect(mockCookiesRemove.calls[1][0]).toEqual('_gid'); diff --git a/src/footer/__snapshots__/footer.component.test.tsx.snap b/src/footer/__snapshots__/footer.component.test.tsx.snap index 57805864..74203387 100644 --- a/src/footer/__snapshots__/footer.component.test.tsx.snap +++ b/src/footer/__snapshots__/footer.component.test.tsx.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Footer component footer renders correctly 1`] = ` +exports[`Footer component > footer renders correctly 1`] = `
{ - let props: FooterProps; - - beforeEach(() => { - props = { - res: undefined, - }; - }); - it('footer renders correctly', () => { const { asFragment } = render( - +