From b56730b1e262a581f9af4b667cda8d6661087ebe Mon Sep 17 00:00:00 2001 From: vanessa Date: Sat, 14 Sep 2024 10:17:33 +0200 Subject: [PATCH] Start unit testing and use preview folders --- client/package.json | 11 +- .../{ => preview}/components/asset/Asset.ts | 0 .../components/asset/AssetBoard.tsx | 4 +- .../components/asset/AssetCard.tsx | 24 +- .../components/asset/LogButton.tsx | 0 .../components/asset/StartStopButton.tsx | 2 +- .../digitaltwins/DigitalTwinTabDataPreview.ts | 0 .../digitaltwins/DigitalTwinsPreview.tsx | 6 +- .../route/digitaltwins/Snackbar.tsx | 0 .../route/digitaltwins/execute}/LogDialog.tsx | 0 .../digitaltwins/execute}/pipelineChecks.ts | 67 +++-- .../digitaltwins/execute}/pipelineHandler.ts | 4 +- .../digitaltwins/execute}/pipelineUtils.ts | 0 client/src/routes.tsx | 2 +- client/src/store/CartAccess.ts | 18 -- client/src/store/cart.slice.ts | 30 --- client/src/store/digitalTwin.slice.ts | 2 +- client/src/store/store.ts | 9 +- client/src/util/gitlab.ts | 2 +- .../unit/components/asset/AssetBoard.test.tsx | 22 +- .../unit/components/asset/AssetCard.test.tsx | 93 +++++++ .../unit/components/asset/LogButton.test.tsx | 2 +- .../components/asset/StartStopButton.test.tsx | 45 ++-- client/test/preview/unit/jest.setup.ts | 9 + .../digitaltwins/DigitalTwinsPreview.test.tsx | 87 +++---- .../routes/digitaltwins/Snackbar.test.tsx | 2 +- .../digitaltwins/execute}/LogDialog.test.tsx | 2 +- .../execute/PipelineChecks.test.ts | 183 ++++++++++++++ .../execute/PipelineHandlers.test.ts | 236 ++++++++++++++++++ .../execute/PipelineUtils.test.ts | 188 ++++++++++++++ .../unit/components/asset/AssetCard.test.tsx | 53 ---- .../digitaltwins/ExecutionFunctions.test.ts | 166 ------------ client/test/unit/util/Store.test.ts | 47 +++- client/test/unit/util/envUtil.test.ts | 1 + client/yarn.lock | 71 ++++-- 35 files changed, 965 insertions(+), 423 deletions(-) rename client/src/{ => preview}/components/asset/Asset.ts (100%) rename client/src/{ => preview}/components/asset/AssetBoard.tsx (89%) rename client/src/{ => preview}/components/asset/AssetCard.tsx (90%) rename client/src/{ => preview}/components/asset/LogButton.tsx (100%) rename client/src/{ => preview}/components/asset/StartStopButton.tsx (94%) rename client/src/{ => preview}/route/digitaltwins/DigitalTwinTabDataPreview.ts (100%) rename client/src/{ => preview}/route/digitaltwins/DigitalTwinsPreview.tsx (91%) rename client/src/{ => preview}/route/digitaltwins/Snackbar.tsx (100%) rename client/src/{route/digitaltwins => preview/route/digitaltwins/execute}/LogDialog.tsx (100%) rename client/src/{route/digitaltwins => preview/route/digitaltwins/execute}/pipelineChecks.ts (73%) rename client/src/{route/digitaltwins => preview/route/digitaltwins/execute}/pipelineHandler.ts (96%) rename client/src/{route/digitaltwins => preview/route/digitaltwins/execute}/pipelineUtils.ts (100%) delete mode 100644 client/src/store/CartAccess.ts delete mode 100644 client/src/store/cart.slice.ts rename client/test/{ => preview}/unit/components/asset/AssetBoard.test.tsx (78%) create mode 100644 client/test/preview/unit/components/asset/AssetCard.test.tsx rename client/test/{ => preview}/unit/components/asset/LogButton.test.tsx (95%) rename client/test/{ => preview}/unit/components/asset/StartStopButton.test.tsx (88%) create mode 100644 client/test/preview/unit/jest.setup.ts rename client/test/{ => preview}/unit/routes/digitaltwins/DigitalTwinsPreview.test.tsx (62%) rename client/test/{ => preview}/unit/routes/digitaltwins/Snackbar.test.tsx (95%) rename client/test/{unit/routes/digitaltwins => preview/unit/routes/digitaltwins/execute}/LogDialog.test.tsx (97%) create mode 100644 client/test/preview/unit/routes/digitaltwins/execute/PipelineChecks.test.ts create mode 100644 client/test/preview/unit/routes/digitaltwins/execute/PipelineHandlers.test.ts create mode 100644 client/test/preview/unit/routes/digitaltwins/execute/PipelineUtils.test.ts delete mode 100644 client/test/unit/components/asset/AssetCard.test.tsx delete mode 100644 client/test/unit/routes/digitaltwins/ExecutionFunctions.test.ts diff --git a/client/package.json b/client/package.json index 61d2af3d4..14f79246b 100644 --- a/client/package.json +++ b/client/package.json @@ -32,7 +32,8 @@ "test:all": "yarn test:unit && yarn test:int && yarn test:e2e", "test:e2e": "yarn build && yarn config:test && npx kill-port 4000 && yarn start >/dev/null & playwright test && npx kill-port 4000", "test:int": "jest -c ./jest.config.json ../test/integration --setupFilesAfterEnv ./test/integration/jest.setup.ts", - "test:unit": "jest -c ./jest.config.json ../test/unit --setupFilesAfterEnv ./test/unit/jest.setup.ts" + "test:unit": "jest -c ./jest.config.json ../test/unit --setupFilesAfterEnv ./test/unit/jest.setup.ts", + "test:preview": "jest -c ./jest.config.json ../test/preview/unit --setupFilesAfterEnv ./test/preview/unit/jest.setup.ts" }, "eslintConfig": { "extends": [ @@ -84,13 +85,13 @@ "@babel/plugin-syntax-flow": "^7.23.3", "@babel/plugin-transform-react-jsx": "^7.23.4", "@playwright/test": "^1.32.1", - "@testing-library/dom": "^9.3.3", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.1.4", - "@testing-library/react": "^14.1.2", + "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.1", "@types/jest": "^29.5.10", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.2.17", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "playwright": "^1.32.1", diff --git a/client/src/components/asset/Asset.ts b/client/src/preview/components/asset/Asset.ts similarity index 100% rename from client/src/components/asset/Asset.ts rename to client/src/preview/components/asset/Asset.ts diff --git a/client/src/components/asset/AssetBoard.tsx b/client/src/preview/components/asset/AssetBoard.tsx similarity index 89% rename from client/src/components/asset/AssetBoard.tsx rename to client/src/preview/components/asset/AssetBoard.tsx index c8004dfc7..94e964250 100644 --- a/client/src/components/asset/AssetBoard.tsx +++ b/client/src/preview/components/asset/AssetBoard.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { Grid } from '@mui/material'; import { GitlabInstance } from 'util/gitlab'; -import { Asset } from './Asset'; -import AssetCardExecute from './AssetCard'; +import { Asset } from 'preview/components/asset/Asset'; +import { AssetCardExecute } from 'preview/components/asset/AssetCard'; const outerGridContainerProps = { container: true, diff --git a/client/src/components/asset/AssetCard.tsx b/client/src/preview/components/asset/AssetCard.tsx similarity index 90% rename from client/src/components/asset/AssetCard.tsx rename to client/src/preview/components/asset/AssetCard.tsx index f73a88e4b..cfb4c4fb0 100644 --- a/client/src/components/asset/AssetCard.tsx +++ b/client/src/preview/components/asset/AssetCard.tsx @@ -8,13 +8,13 @@ import styled from '@emotion/styled'; import DigitalTwin, { formatName } from 'util/gitlabDigitalTwin'; import { GitlabInstance } from 'util/gitlab'; import { getAuthority } from 'util/envUtil'; -import CustomSnackbar from 'route/digitaltwins/Snackbar'; +import CustomSnackbar from 'preview/route/digitaltwins/Snackbar'; import { useDispatch, useSelector } from 'react-redux'; import { setDigitalTwin, selectDigitalTwinByName, } from 'store/digitalTwin.slice'; -import LogDialog from 'route/digitaltwins/LogDialog'; +import LogDialog from 'preview/route/digitaltwins/execute/LogDialog'; import StartStopButton from './StartStopButton'; import LogButton from './LogButton'; import { Asset } from './Asset'; @@ -128,18 +128,22 @@ function AssetCardExecute({ asset }: AssetCardExecuteProps) { const dispatch = useDispatch(); useEffect(() => { + const initialize = async () => { const gitlabInstance = new GitlabInstance( sessionStorage.getItem('username') || '', getAuthority(), sessionStorage.getItem('access_token') || '', ); - gitlabInstance.init(); - dispatch( - setDigitalTwin({ - assetName: asset.name, - digitalTwin: new DigitalTwin(asset.name, gitlabInstance), - }), - ); + await gitlabInstance.init(); + dispatch( + setDigitalTwin({ + assetName: asset.name, + digitalTwin: new DigitalTwin(asset.name, gitlabInstance), + }), + ); + } + + initialize(); }, [asset.name, dispatch]); const digitalTwin = useSelector(selectDigitalTwinByName(asset.name)); @@ -175,4 +179,4 @@ function AssetCardExecute({ asset }: AssetCardExecuteProps) { ); } -export default AssetCardExecute; +export { AssetCard, AssetCardExecute, CardButtonsContainerExecute }; diff --git a/client/src/components/asset/LogButton.tsx b/client/src/preview/components/asset/LogButton.tsx similarity index 100% rename from client/src/components/asset/LogButton.tsx rename to client/src/preview/components/asset/LogButton.tsx diff --git a/client/src/components/asset/StartStopButton.tsx b/client/src/preview/components/asset/StartStopButton.tsx similarity index 94% rename from client/src/components/asset/StartStopButton.tsx rename to client/src/preview/components/asset/StartStopButton.tsx index fc04a9091..65969fca4 100644 --- a/client/src/components/asset/StartStopButton.tsx +++ b/client/src/preview/components/asset/StartStopButton.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { useState, Dispatch, SetStateAction } from 'react'; import { AlertColor, Button, CircularProgress } from '@mui/material'; -import { handleButtonClick } from 'route/digitaltwins/pipelineHandler'; +import { handleButtonClick } from 'preview/route/digitaltwins/execute/pipelineHandler'; import { useSelector, useDispatch } from 'react-redux'; import { selectDigitalTwinByName } from 'store/digitalTwin.slice'; diff --git a/client/src/route/digitaltwins/DigitalTwinTabDataPreview.ts b/client/src/preview/route/digitaltwins/DigitalTwinTabDataPreview.ts similarity index 100% rename from client/src/route/digitaltwins/DigitalTwinTabDataPreview.ts rename to client/src/preview/route/digitaltwins/DigitalTwinTabDataPreview.ts diff --git a/client/src/route/digitaltwins/DigitalTwinsPreview.tsx b/client/src/preview/route/digitaltwins/DigitalTwinsPreview.tsx similarity index 91% rename from client/src/route/digitaltwins/DigitalTwinsPreview.tsx rename to client/src/preview/route/digitaltwins/DigitalTwinsPreview.tsx index 33953b334..4bded46a8 100644 --- a/client/src/route/digitaltwins/DigitalTwinsPreview.tsx +++ b/client/src/preview/route/digitaltwins/DigitalTwinsPreview.tsx @@ -4,11 +4,11 @@ import { Typography } from '@mui/material'; import Layout from 'page/Layout'; import TabComponent from 'components/tab/TabComponent'; import { TabData } from 'components/tab/subcomponents/TabRender'; -import { Asset } from 'components/asset/Asset'; -import AssetBoard from 'components/asset/AssetBoard'; +import { Asset } from 'preview/components/asset/Asset'; +import AssetBoard from 'preview/components/asset/AssetBoard'; import { GitlabInstance } from 'util/gitlab'; import { getAuthority } from 'util/envUtil'; -import tabs from './DigitalTwinTabData'; +import tabs from '../../../route/digitaltwins/DigitalTwinTabData'; const createDTTab = ( subfolders: Asset[], diff --git a/client/src/route/digitaltwins/Snackbar.tsx b/client/src/preview/route/digitaltwins/Snackbar.tsx similarity index 100% rename from client/src/route/digitaltwins/Snackbar.tsx rename to client/src/preview/route/digitaltwins/Snackbar.tsx diff --git a/client/src/route/digitaltwins/LogDialog.tsx b/client/src/preview/route/digitaltwins/execute/LogDialog.tsx similarity index 100% rename from client/src/route/digitaltwins/LogDialog.tsx rename to client/src/preview/route/digitaltwins/execute/LogDialog.tsx diff --git a/client/src/route/digitaltwins/pipelineChecks.ts b/client/src/preview/route/digitaltwins/execute/pipelineChecks.ts similarity index 73% rename from client/src/route/digitaltwins/pipelineChecks.ts rename to client/src/preview/route/digitaltwins/execute/pipelineChecks.ts index 82f47c917..bddaa597d 100644 --- a/client/src/route/digitaltwins/pipelineChecks.ts +++ b/client/src/preview/route/digitaltwins/execute/pipelineChecks.ts @@ -2,8 +2,8 @@ import { Dispatch, SetStateAction } from 'react'; import { useDispatch } from 'react-redux'; import { AlertColor } from '@mui/material'; import DigitalTwin, { formatName } from 'util/gitlabDigitalTwin'; -import { fetchJobLogs, updatePipelineStateOnCompletion } from './pipelineUtils'; -import { setSnackbar } from './pipelineHandler'; +import { fetchJobLogs, updatePipelineStateOnCompletion } from 'preview/route/digitaltwins/execute/pipelineUtils'; +import { setSnackbar } from 'preview/route/digitaltwins/execute/pipelineHandler'; interface PipelineStatusParams { setButtonText: Dispatch>; @@ -21,7 +21,7 @@ const delay = (ms: number) => const hasTimedOut = (startTime: number) => Date.now() - startTime > MAX_EXECUTION_TIME; -const handleTimeout = ( +export const handleTimeout = ( DTName: string, setButtonText: Dispatch>, setLogButtonDisabled: Dispatch>, @@ -40,7 +40,7 @@ const handleTimeout = ( setLogButtonDisabled(false); }; -const checkFirstPipelineStatus = async ({ +export const checkFirstPipelineStatus = async ({ setButtonText, digitalTwin, setLogButtonDisabled, @@ -107,7 +107,31 @@ const checkFirstPipelineStatus = async ({ } }; -const checkSecondPipelineStatus = async ({ +export const handlePipelineCompletion = async ( + pipelineId: number, + digitalTwin: DigitalTwin, + setButtonText: Dispatch>, + setLogButtonDisabled: Dispatch>, + dispatch: ReturnType, + setSnackbarMessage: Dispatch>, + setSnackbarSeverity: Dispatch>, + setSnackbarOpen: Dispatch>, + pipelineStatus: 'success' | 'failed' +) => { + const jobLogs = await fetchJobLogs(digitalTwin.gitlabInstance, pipelineId); + updatePipelineStateOnCompletion(digitalTwin, jobLogs, setButtonText, setLogButtonDisabled, dispatch); + if (pipelineStatus === 'failed') { + setSnackbar( + `Execution failed for ${formatName(digitalTwin.DTName)}`, + 'error', + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + ); + } +}; + +export const checkSecondPipelineStatus = async ({ setButtonText, digitalTwin, setLogButtonDisabled, @@ -122,34 +146,24 @@ const checkSecondPipelineStatus = async ({ setSnackbarSeverity: Dispatch>; setSnackbarOpen: Dispatch>; }) => { + const pipelineId = digitalTwin.pipelineId! + 1; const pipelineStatus = await digitalTwin.gitlabInstance.getPipelineStatus( digitalTwin.gitlabInstance.projectId!, - digitalTwin.pipelineId! + 1, + pipelineId, ); if (pipelineStatus === 'success' || pipelineStatus === 'failed') { - const pipelineIdJobs = digitalTwin.pipelineId! + 1; - const jobLogs = await fetchJobLogs( - digitalTwin.gitlabInstance, - pipelineIdJobs, - ); - updatePipelineStateOnCompletion( + await handlePipelineCompletion( + pipelineId, digitalTwin, - jobLogs, setButtonText, setLogButtonDisabled, dispatch, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + pipelineStatus ); - - if (pipelineStatus === 'failed') { - setSnackbar( - `Execution failed for ${formatName(digitalTwin.DTName)}`, - 'error', - setSnackbarMessage, - setSnackbarSeverity, - setSnackbarOpen, - ); - } } else if (hasTimedOut(startTime)) { handleTimeout( digitalTwin.DTName, @@ -161,7 +175,7 @@ const checkSecondPipelineStatus = async ({ ); } else { await delay(5000); - checkSecondPipelineStatus({ + await checkSecondPipelineStatus({ setButtonText, digitalTwin, setLogButtonDisabled, @@ -174,7 +188,8 @@ const checkSecondPipelineStatus = async ({ } }; -const startPipelineStatusCheck = ( + +export const startPipelineStatusCheck = ( params: PipelineStatusParams & { setSnackbarMessage: Dispatch>; setSnackbarSeverity: Dispatch>; @@ -184,5 +199,3 @@ const startPipelineStatusCheck = ( const startTime = Date.now(); checkFirstPipelineStatus({ ...params, startTime }); }; - -export default startPipelineStatusCheck; diff --git a/client/src/route/digitaltwins/pipelineHandler.ts b/client/src/preview/route/digitaltwins/execute/pipelineHandler.ts similarity index 96% rename from client/src/route/digitaltwins/pipelineHandler.ts rename to client/src/preview/route/digitaltwins/execute/pipelineHandler.ts index e42ad46be..0db9fc8a3 100644 --- a/client/src/route/digitaltwins/pipelineHandler.ts +++ b/client/src/preview/route/digitaltwins/execute/pipelineHandler.ts @@ -7,7 +7,7 @@ import { updatePipelineState, updatePipelineStateOnStop, } from './pipelineUtils'; -import startPipelineStatusCheck from './pipelineChecks'; +import { startPipelineStatusCheck } from './pipelineChecks'; export const handleButtonClick = ( buttonText: string, @@ -107,7 +107,7 @@ export const handleStop = async ( } }; -const stopPipelines = async (digitalTwin: DigitalTwin) => { +export const stopPipelines = async (digitalTwin: DigitalTwin) => { if (digitalTwin.gitlabInstance.projectId && digitalTwin.pipelineId) { await digitalTwin.stop( digitalTwin.gitlabInstance.projectId, diff --git a/client/src/route/digitaltwins/pipelineUtils.ts b/client/src/preview/route/digitaltwins/execute/pipelineUtils.ts similarity index 100% rename from client/src/route/digitaltwins/pipelineUtils.ts rename to client/src/preview/route/digitaltwins/execute/pipelineUtils.ts diff --git a/client/src/routes.tsx b/client/src/routes.tsx index 8ab9f0427..676481ae0 100644 --- a/client/src/routes.tsx +++ b/client/src/routes.tsx @@ -4,7 +4,7 @@ import LayoutPublic from 'page/LayoutPublic'; import PrivateRoute from 'route/auth/PrivateRoute'; import Library from './route/library/Library'; import DigitalTwins from './route/digitaltwins/DigitalTwins'; -import DigitalTwinsPreview from './route/digitaltwins/DigitalTwinsPreview'; +import DigitalTwinsPreview from './preview/route/digitaltwins/DigitalTwinsPreview'; import SignIn from './route/auth/Signin'; import Account from './route/auth/Account'; diff --git a/client/src/store/CartAccess.ts b/client/src/store/CartAccess.ts deleted file mode 100644 index 4cedcfc03..000000000 --- a/client/src/store/CartAccess.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useDispatch, useSelector } from 'react-redux'; -import { Asset } from 'components/asset/Asset'; -import * as cart from './cart.slice'; -import { RootState } from './store'; - -function useCart() { - const dispatch = useDispatch(); - const state = useSelector((store: RootState) => store.cart); - const actions = { - add: (asset: Asset) => dispatch(cart.addToCart(asset)), - remove: (asset: Asset) => dispatch(cart.removeFromCart(asset)), - clear: () => dispatch(cart.clearCart()), - }; - - return { state, actions }; -} - -export default useCart; diff --git a/client/src/store/cart.slice.ts b/client/src/store/cart.slice.ts deleted file mode 100644 index 28d651cff..000000000 --- a/client/src/store/cart.slice.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { Asset } from 'components/asset/Asset'; - -export interface CartState { - assets: Asset[]; -} - -const initState: CartState = { - assets: [], -}; - -const cartSlice = createSlice({ - name: 'cart', - initialState: initState, - reducers: { - addToCart: (state: CartState, action: PayloadAction) => { - if (!state.assets.find((asset) => asset.path === action.payload.path)) - state.assets.push(action.payload); - }, - removeFromCart: (state: CartState, action: PayloadAction) => { - state.assets = state.assets.filter((a) => a.path !== action.payload.path); - }, - clearCart: (state: CartState) => { - state.assets = []; - }, - }, -}); - -export const { addToCart, removeFromCart, clearCart } = cartSlice.actions; -export default cartSlice.reducer; diff --git a/client/src/store/digitalTwin.slice.ts b/client/src/store/digitalTwin.slice.ts index 36d1f6bb7..f6ef5f990 100644 --- a/client/src/store/digitalTwin.slice.ts +++ b/client/src/store/digitalTwin.slice.ts @@ -1,6 +1,6 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import DigitalTwin from 'util/gitlabDigitalTwin'; -import { JobLog } from 'components/asset/StartStopButton'; +import { JobLog } from 'preview/components/asset/StartStopButton'; import { RootState } from './store'; interface DigitalTwinState { diff --git a/client/src/store/store.ts b/client/src/store/store.ts index 7bcf42f01..d69c2ecd6 100644 --- a/client/src/store/store.ts +++ b/client/src/store/store.ts @@ -2,18 +2,23 @@ import { combineReducers } from 'redux'; import { configureStore } from '@reduxjs/toolkit'; import menuSlice from './menu.slice'; import authSlice from './auth.slice'; -import cartReducer from './cart.slice'; import digitalTwinSlice from './digitalTwin.slice'; const rootReducer = combineReducers({ menu: menuSlice, auth: authSlice, - cart: cartReducer, digitalTwin: digitalTwinSlice, }); const store = configureStore({ reducer: rootReducer, + middleware: getDefaultMiddleware => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: ['digitalTwin/setDigitalTwin'], + ignoredPaths: ['digitalTwin.Test Asset', 'payload.digitalTwin'], + }, + }), }); export type RootState = ReturnType; diff --git a/client/src/util/gitlab.ts b/client/src/util/gitlab.ts index a0d85dc17..0685153d2 100644 --- a/client/src/util/gitlab.ts +++ b/client/src/util/gitlab.ts @@ -1,5 +1,5 @@ import { Gitlab } from '@gitbeaker/rest'; -import { Asset } from '../components/asset/Asset'; +import { Asset } from '../preview/components/asset/Asset'; const GROUP_NAME = 'DTaaS'; const DT_DIRECTORY = 'digital_twins'; diff --git a/client/test/unit/components/asset/AssetBoard.test.tsx b/client/test/preview/unit/components/asset/AssetBoard.test.tsx similarity index 78% rename from client/test/unit/components/asset/AssetBoard.test.tsx rename to client/test/preview/unit/components/asset/AssetBoard.test.tsx index 0003ffab4..057914ece 100644 --- a/client/test/unit/components/asset/AssetBoard.test.tsx +++ b/client/test/preview/unit/components/asset/AssetBoard.test.tsx @@ -1,14 +1,12 @@ import * as React from 'react'; -import { render, screen } from '@testing-library/react'; -import AssetBoard from 'components/asset/AssetBoard'; -import { Asset } from 'components/asset/Asset'; +import { render, screen, act } from '@testing-library/react'; +import AssetBoard from 'preview/components/asset/AssetBoard'; +import { Asset } from 'preview/components/asset/Asset'; import { GitlabInstance } from 'util/gitlab'; import '@testing-library/jest-dom'; import store from 'store/store'; import { Provider } from 'react-redux'; -jest.unmock('components/asset/AssetBoard'); - jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), })); @@ -21,8 +19,8 @@ jest.mock('util/envUtil', () => ({ jest.mock(''); const assetsMock: Asset[] = [ - { name: 'Asset1', path: 'path1', description: 'Description1' }, - { name: 'Asset2', path: 'path2', description: 'Description2' }, + { name: 'Test Asset', path: 'path1', description: 'Description1' }, + { name: 'Test Asset', path: 'path2', description: 'Description2' }, ]; jest.mock('util/gitlab', () => ({ @@ -38,17 +36,19 @@ const mockGitlabInstance = new GitlabInstance( ); describe('AssetBoard', () => { - it('renders AssetCard components for each asset', () => { - render( + it('renders AssetCard components for each asset', async() => { + + await act( async () => + render( - ); , - ); + )); + const cards = screen.getAllByText(/Description/); expect(cards).toHaveLength(assetsMock.length); }); diff --git a/client/test/preview/unit/components/asset/AssetCard.test.tsx b/client/test/preview/unit/components/asset/AssetCard.test.tsx new file mode 100644 index 000000000..d0915fd87 --- /dev/null +++ b/client/test/preview/unit/components/asset/AssetCard.test.tsx @@ -0,0 +1,93 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { AssetCard, AssetCardExecute, CardButtonsContainerExecute } from 'preview/components/asset/AssetCard'; +import { setDigitalTwin } from 'store/digitalTwin.slice'; +import { GitlabInstance } from 'util/gitlab'; +import DigitalTwin from 'util/gitlabDigitalTwin'; +import { Asset } from 'preview/components/asset/Asset'; +import store from 'store/store'; +import '@testing-library/jest-dom'; + +jest.deepUnmock('preview/components/asset/AssetCard'); + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), +})); + +jest.mock('util/gitlab'); +jest.mock('util/envUtil', () => ({ + getAuthority: jest.fn(() => 'https://example.com'), +})); + +describe('AssetCard', () => { + const asset: Asset = { + name: 'Test Asset', + description: 'Test Description', + path: 'Test Path', + }; + + it('should render AssetCard with correct props', () => { + render( + + + + ); + expect(screen.getByText('Test Asset')).toBeInTheDocument(); + expect(screen.getByText('Test Description')).toBeInTheDocument(); + }); +}); + +describe('CardButtonsContainerExecute', () => { + it('should render StartStopButton and LogButton', () => { + const setSnackbarOpen = jest.fn(); + const setSnackbarMessage = jest.fn(); + const setSnackbarSeverity = jest.fn(); + const setShowLog = jest.fn(); + + render( + + + + ); + + // Verifica che i pulsanti siano renderizzati + expect(screen.getByText('Start')).toBeInTheDocument(); + expect(screen.getByText('Log')).toBeInTheDocument(); + }); +}); + + +describe('AssetCardExecute', () => { + const asset: Asset = { + name: 'Test Asset', + description: 'Test Description', + path: 'Test Path', + }; + + beforeEach(() => { + (GitlabInstance as jest.Mock).mockImplementation(() => ({ + init: jest.fn(), + })); + store.dispatch(setDigitalTwin({ assetName: asset.name, digitalTwin: new DigitalTwin(asset.name, new GitlabInstance('user1', 'authority', 'token1')) })); + }); + + it('should render AssetCardExecute and handle interactions', async () => { + render( + + + + ); + + expect(screen.getByText('Test Asset')).toBeInTheDocument(); + expect(screen.getByText('Test Description')).toBeInTheDocument(); + expect(screen.getByText('Start')).toBeInTheDocument(); + expect(screen.getByText('Log')).toBeInTheDocument(); + }); +}); diff --git a/client/test/unit/components/asset/LogButton.test.tsx b/client/test/preview/unit/components/asset/LogButton.test.tsx similarity index 95% rename from client/test/unit/components/asset/LogButton.test.tsx rename to client/test/preview/unit/components/asset/LogButton.test.tsx index 41b6e4ad7..b299c50f4 100644 --- a/client/test/unit/components/asset/LogButton.test.tsx +++ b/client/test/preview/unit/components/asset/LogButton.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; -import LogButton from 'components/asset/LogButton'; +import LogButton from 'preview/components/asset/LogButton'; const mockSetShowLog = jest.fn(); diff --git a/client/test/unit/components/asset/StartStopButton.test.tsx b/client/test/preview/unit/components/asset/StartStopButton.test.tsx similarity index 88% rename from client/test/unit/components/asset/StartStopButton.test.tsx rename to client/test/preview/unit/components/asset/StartStopButton.test.tsx index c04f0e73e..4430b5b9d 100644 --- a/client/test/unit/components/asset/StartStopButton.test.tsx +++ b/client/test/preview/unit/components/asset/StartStopButton.test.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import StartStopButton from 'components/asset/StartStopButton'; -import { handleButtonClick } from 'route/digitaltwins/pipelineHandler'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import StartStopButton from 'preview/components/asset/StartStopButton'; +import { handleButtonClick } from 'preview/route/digitaltwins/execute/pipelineHandler'; import { useSelector, useDispatch } from 'react-redux'; import { RootState } from 'store/store'; import '@testing-library/jest-dom'; -jest.mock('route/digitaltwins/pipelineHandler', () => ({ +jest.mock('preview/route/digitaltwins/execute/pipelineHandler', () => ({ handleButtonClick: jest.fn(), })); @@ -40,8 +40,9 @@ describe('StartStopButton', () => { jest.clearAllMocks(); }); - test('renders with start button text initially', () => { - render( + test('renders with start button text initially', async () => { + await act( async () => + render( { setSnackbarSeverity={mockSetSnackbarSeverity} setLogButtonDisabled={mockSetLogButtonDisabled} />, - ); + )) expect(screen.getByText('Start')).toBeInTheDocument(); }); - test('renders circular progress when pipelineLoading is true', () => { + test('renders circular progress when pipelineLoading is true', async () => { (useSelector as jest.Mock).mockImplementationOnce((selector) => selector({ digitalTwin: { @@ -62,7 +63,7 @@ describe('StartStopButton', () => { }, } as unknown as RootState), ); - + await act( async () => render( { setSnackbarSeverity={mockSetSnackbarSeverity} setLogButtonDisabled={mockSetLogButtonDisabled} />, - ); + )); expect(screen.getByTestId('circular-progress')).toBeInTheDocument(); expect(screen.getByText('Start')).toBeInTheDocument(); }); - test('does not render circular progress when pipelineLoading is false', () => { - render( + test('does not render circular progress when pipelineLoading is false', async () => { + await act( async () => + render( { setSnackbarSeverity={mockSetSnackbarSeverity} setLogButtonDisabled={mockSetLogButtonDisabled} />, - ); + )); expect(screen.queryByTestId('circular-progress')).not.toBeInTheDocument(); expect(screen.getByText('Start')).toBeInTheDocument(); }); - test('calls handleButtonClick with correct arguments when button is clicked', () => { - render( + test('calls handleButtonClick with correct arguments when button is clicked', async () => { + await act( async () => + render( { setSnackbarSeverity={mockSetSnackbarSeverity} setLogButtonDisabled={mockSetLogButtonDisabled} />, - ); + )); fireEvent.click(screen.getByText('Start')); @@ -134,7 +137,8 @@ describe('StartStopButton', () => { }, ); - render( + await act( async () => + render( { setSnackbarSeverity={mockSetSnackbarSeverity} setLogButtonDisabled={mockSetLogButtonDisabled} />, - ); + )); fireEvent.click(screen.getByText('Start')); @@ -171,7 +175,8 @@ describe('StartStopButton', () => { }, ); - render( + await act( async () => + render( { setSnackbarSeverity={mockSetSnackbarSeverity} setLogButtonDisabled={mockSetLogButtonDisabled} />, - ); + )); fireEvent.click(screen.getByText('Start')); diff --git a/client/test/preview/unit/jest.setup.ts b/client/test/preview/unit/jest.setup.ts new file mode 100644 index 000000000..53953aa4b --- /dev/null +++ b/client/test/preview/unit/jest.setup.ts @@ -0,0 +1,9 @@ +import '@testing-library/jest-dom'; +import 'test/__mocks__/global_mocks'; +import 'test/__mocks__/unit/page_mocks'; +import 'test/__mocks__/unit/component_mocks'; +import 'test/__mocks__/unit/module_mocks'; + +beforeEach(() => { + jest.resetAllMocks(); +}); diff --git a/client/test/unit/routes/digitaltwins/DigitalTwinsPreview.test.tsx b/client/test/preview/unit/routes/digitaltwins/DigitalTwinsPreview.test.tsx similarity index 62% rename from client/test/unit/routes/digitaltwins/DigitalTwinsPreview.test.tsx rename to client/test/preview/unit/routes/digitaltwins/DigitalTwinsPreview.test.tsx index 57f9ebcb0..82a9d4572 100644 --- a/client/test/unit/routes/digitaltwins/DigitalTwinsPreview.test.tsx +++ b/client/test/preview/unit/routes/digitaltwins/DigitalTwinsPreview.test.tsx @@ -1,20 +1,13 @@ import * as React from 'react'; -import DigitalTwinsPreview, { - fetchSubfolders, -} from 'route/digitaltwins/DigitalTwinsPreview'; -import tabs from 'route/digitaltwins/DigitalTwinTabData'; -import { - InitRouteTests, - itDisplaysContentOfExecuteTab, - itHasCorrectExecuteTabNameInDTIframe, -} from 'test/unit/unit.testUtil'; +import DigitalTwinsPreview, { fetchSubfolders } from 'preview/route/digitaltwins/DigitalTwinsPreview'; +import tabs from 'preview/route/digitaltwins/DigitalTwinTabDataPreview'; import store from 'store/store'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import '@testing-library/jest-dom'; import { GitlabInstance } from 'util/gitlab'; -import { renderHook, act } from '@testing-library/react'; -import { Asset } from 'components/asset/Asset'; +import { render, screen, act, renderHook } from '@testing-library/react'; +import { Asset } from 'preview/components/asset/Asset'; jest.mock('react-oidc-context', () => ({ ...jest.requireActual('react-oidc-context'), @@ -34,20 +27,42 @@ jest.mock('util/envUtil', () => ({ })); describe('Digital Twins Preview', () => { - const tabLabels: string[] = []; - tabs.forEach((tab) => tabLabels.push(tab.label)); + const tabLabels: string[] = tabs.map(tab => tab.label); + + it('should render the label of the Execute tab', () => { + render( + + + + + + ); + const executeTab = tabs.find(tab => tab.label === 'Execute'); - InitRouteTests( - - - - - , - ); + if (executeTab) { + expect( + screen.getByRole('tab', { name: executeTab.label }) + ).toBeInTheDocument(); + } + }); - itDisplaysContentOfExecuteTab(tabs); + it("should render the Iframe component on DT page for the 'Execute' tab with the correct title", () => { + render( + + + + + + ); + + const executeTabLabel = tabLabels.find(label => label === 'Execute'); + + if (executeTabLabel) { + const tabElement = screen.getByRole('tab', { name: executeTabLabel }); + expect(tabElement).toBeTruthy(); + } + }); - itHasCorrectExecuteTabNameInDTIframe(tabLabels); it('should call getDTSubfolders and update subfolders state', async () => { const mockGitlabInstance = new GitlabInstance( @@ -57,17 +72,14 @@ describe('Digital Twins Preview', () => { ); const { result } = renderHook(() => React.useState([])); - const setSubfolders: React.Dispatch> = - result.current[1]; + const setSubfolders: React.Dispatch> = result.current[1]; await act(async () => { await fetchSubfolders(mockGitlabInstance, setSubfolders, jest.fn()); }); expect(mockGitlabInstance.init).toHaveBeenCalled(); - expect(mockGitlabInstance.getDTSubfolders).toHaveBeenCalledWith( - 'mockedProjectId', - ); + expect(mockGitlabInstance.getDTSubfolders).toHaveBeenCalledWith('mockedProjectId'); expect(result.current[0]).toEqual([]); }); @@ -84,8 +96,7 @@ describe('Digital Twins Preview', () => { const { result } = renderHook(() => React.useState([])); - const setSubfolders: React.Dispatch> = - result.current[1]; + const setSubfolders: React.Dispatch> = result.current[1]; await act(async () => { await fetchSubfolders(mockGitlabInstance, setSubfolders, jest.fn()); @@ -102,21 +113,13 @@ describe('Digital Twins Preview', () => { 'https://example.com', 'access_token', ); - jest - .spyOn(mockGitlabInstance, 'init') - .mockRejectedValue(new Error('Initialization failed')); + jest.spyOn(mockGitlabInstance, 'init').mockRejectedValue(new Error('Initialization failed')); - const { result: subfoldersResult } = renderHook(() => - React.useState([]), - ); - const { result: errorResult } = renderHook(() => - React.useState(null), - ); + const { result: subfoldersResult } = renderHook(() => React.useState([])); + const { result: errorResult } = renderHook(() => React.useState(null)); - const setSubfolders: React.Dispatch> = - subfoldersResult.current[1]; - const setError: React.Dispatch> = - errorResult.current[1]; + const setSubfolders: React.Dispatch> = subfoldersResult.current[1]; + const setError: React.Dispatch> = errorResult.current[1]; await act(async () => { await fetchSubfolders(mockGitlabInstance, setSubfolders, setError); diff --git a/client/test/unit/routes/digitaltwins/Snackbar.test.tsx b/client/test/preview/unit/routes/digitaltwins/Snackbar.test.tsx similarity index 95% rename from client/test/unit/routes/digitaltwins/Snackbar.test.tsx rename to client/test/preview/unit/routes/digitaltwins/Snackbar.test.tsx index 632fdba7f..c5602f27c 100644 --- a/client/test/unit/routes/digitaltwins/Snackbar.test.tsx +++ b/client/test/preview/unit/routes/digitaltwins/Snackbar.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { render, screen, fireEvent, act } from '@testing-library/react'; import '@testing-library/jest-dom'; -import CustomSnackbar from 'route/digitaltwins/Snackbar'; // Adjust the import path accordingly +import CustomSnackbar from 'preview/route/digitaltwins/Snackbar'; // Adjust the import path accordingly describe('CustomSnackbar', () => { it('renders the snackbar with the correct message and severity', () => { diff --git a/client/test/unit/routes/digitaltwins/LogDialog.test.tsx b/client/test/preview/unit/routes/digitaltwins/execute/LogDialog.test.tsx similarity index 97% rename from client/test/unit/routes/digitaltwins/LogDialog.test.tsx rename to client/test/preview/unit/routes/digitaltwins/execute/LogDialog.test.tsx index 1b065f311..969c84a8a 100644 --- a/client/test/unit/routes/digitaltwins/LogDialog.test.tsx +++ b/client/test/preview/unit/routes/digitaltwins/execute/LogDialog.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; -import LogDialog from 'route/digitaltwins/LogDialog'; +import LogDialog from 'preview/route/digitaltwins/execute/LogDialog'; import { useSelector } from 'react-redux'; jest.mock('react-redux', () => ({ diff --git a/client/test/preview/unit/routes/digitaltwins/execute/PipelineChecks.test.ts b/client/test/preview/unit/routes/digitaltwins/execute/PipelineChecks.test.ts new file mode 100644 index 000000000..472de62a0 --- /dev/null +++ b/client/test/preview/unit/routes/digitaltwins/execute/PipelineChecks.test.ts @@ -0,0 +1,183 @@ +import { Dispatch } from 'react'; +import { startPipelineStatusCheck, handleTimeout, checkFirstPipelineStatus } from 'preview/route/digitaltwins/execute/pipelineChecks'; +import DigitalTwin from 'util/gitlabDigitalTwin'; +import { fetchJobLogs, updatePipelineStateOnCompletion } from 'preview/route/digitaltwins/execute/pipelineUtils'; +import { setSnackbar } from 'preview/route/digitaltwins/execute/pipelineHandler'; +import { useDispatch } from 'react-redux'; +import { AlertColor } from '@mui/material'; + +jest.mock('preview/route/digitaltwins/execute/pipelineUtils', () => ({ + fetchJobLogs: jest.fn(), + updatePipelineStateOnCompletion: jest.fn(), +})); + +jest.mock('preview/route/digitaltwins/execute/pipelineHandler', () => ({ + setSnackbar: jest.fn(), +})); + +//const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +describe('Pipeline Status Tests', () => { + let setButtonText: Dispatch>; + let setLogButtonDisabled: Dispatch>; + let setSnackbarMessage: Dispatch>; + let setSnackbarSeverity: Dispatch>; + let setSnackbarOpen: Dispatch>; + let dispatch: ReturnType; + let digitalTwin: DigitalTwin; + let startTime: number; + + beforeEach(() => { + setButtonText = jest.fn(); + setLogButtonDisabled = jest.fn(); + setSnackbarMessage = jest.fn(); + setSnackbarSeverity = jest.fn(); + setSnackbarOpen = jest.fn(); + dispatch = jest.fn(); + + digitalTwin = { + DTName: 'Test Twin', + gitlabInstance: { + getPipelineStatus: jest.fn(), + projectId: '123', + }, + pipelineId: 100, + } as unknown as DigitalTwin; + + //jest.useFakeTimers(); + //Date.now = jest.fn(() => 1487076708000) + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('startPipelineStatusCheck', () => { + it('should call checkFirstPipelineStatus with the correct params', async () => { + const originalDateNow = Date.now; + const mockDateNow = jest.fn(() => 1622547600000); // Example timestamp + global.Date.now = mockDateNow; + + await startPipelineStatusCheck({ + setButtonText, + digitalTwin, + setLogButtonDisabled, + dispatch, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + }); + + expect(mockDateNow).toHaveBeenCalled(); + + global.Date.now = originalDateNow; // Restore the original Date.now function + }); + }); + + describe('handleTimeout', () => { + it('should set appropriate values on timeout', () => { + handleTimeout( + digitalTwin.DTName, + setButtonText, + setLogButtonDisabled, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + ); + + expect(setButtonText).toHaveBeenCalledWith('Start'); + expect(setLogButtonDisabled).toHaveBeenCalledWith(false); + expect(setSnackbar).toHaveBeenCalledWith( + 'Execution timed out for Test Twin', + 'error', + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + ); + }); + }); + + describe('checkFirstPipelineStatus', () => { + it('should call checkSecondPipelineStatus if pipeline is successful', async () => { + (digitalTwin.gitlabInstance.getPipelineStatus as jest.Mock).mockResolvedValue('success'); + + const spy = jest.spyOn(require('preview/route/digitaltwins/execute/pipelineChecks.ts'), 'checkSecondPipelineStatus'); + await checkFirstPipelineStatus({ + setButtonText, + digitalTwin, + setLogButtonDisabled, + dispatch, + startTime, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + }); + + expect(digitalTwin.gitlabInstance.getPipelineStatus).toHaveBeenCalledWith('123', 100); + expect(spy).toHaveBeenCalled(); + }); + + it('should handle timeout correctly if execution exceeds max time', async () => { + jest.spyOn(require('preview/route/digitaltwins/execute/pipelineChecks.ts'), 'handleTimeout').mockImplementation(() => {}); + + await checkFirstPipelineStatus({ + setButtonText, + digitalTwin, + setLogButtonDisabled, + dispatch, + startTime, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + }); + + // Use more flexible matching if the exact match is not crucial + expect(handleTimeout).toHaveBeenCalledWith( + expect.any(String), // or "Test Twin" if the value is known and fixed + expect.any(Function), + expect.any(Function), + expect.any(Function), + expect.any(Function), + expect.any(Function), + ); + }); + + it('should handle failed pipelines correctly', async () => { + (digitalTwin.gitlabInstance.getPipelineStatus as jest.Mock).mockResolvedValue('failed'); + (fetchJobLogs as jest.Mock).mockResolvedValue('mock logs'); + + await checkFirstPipelineStatus({ + setButtonText, + digitalTwin, + setLogButtonDisabled, + dispatch, + startTime, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + }); + + expect(fetchJobLogs).toHaveBeenCalledWith(digitalTwin.gitlabInstance, 100); + expect(updatePipelineStateOnCompletion).toHaveBeenCalled(); + }); + + it('handleTimeout should set correct states and call setSnackbar', () => { + const mockSetButtonText = jest.fn(); + const mockSetLogButtonDisabled = jest.fn(); + const mockSetSnackbarMessage = jest.fn(); + const mockSetSnackbarSeverity = jest.fn(); + const mockSetSnackbarOpen = jest.fn(); + + handleTimeout('DTName', mockSetButtonText, mockSetLogButtonDisabled, mockSetSnackbarMessage, mockSetSnackbarSeverity, mockSetSnackbarOpen); + + // Verify that handleTimeout triggers the expected state changes or function calls + expect(mockSetButtonText).toHaveBeenCalledWith('Start'); + expect(mockSetLogButtonDisabled).toHaveBeenCalledWith(false); + // If handleTimeout indirectly leads to setSnackbar being called, verify the relevant function calls + // For example, if setting a message, severity, and opening the snackbar are the required actions: + expect(mockSetSnackbarMessage).toHaveBeenCalledWith(expect.any(String)); + expect(mockSetSnackbarSeverity).toHaveBeenCalledWith('error'); + expect(mockSetSnackbarOpen).toHaveBeenCalledWith(true); + }); + }); +}); diff --git a/client/test/preview/unit/routes/digitaltwins/execute/PipelineHandlers.test.ts b/client/test/preview/unit/routes/digitaltwins/execute/PipelineHandlers.test.ts new file mode 100644 index 000000000..769b95c08 --- /dev/null +++ b/client/test/preview/unit/routes/digitaltwins/execute/PipelineHandlers.test.ts @@ -0,0 +1,236 @@ +import { Dispatch, SetStateAction } from 'react'; +import { AlertColor } from '@mui/material'; +import DigitalTwin, { formatName } from 'util/gitlabDigitalTwin'; +import { useDispatch } from 'react-redux'; +import { + updatePipelineState, + updatePipelineStateOnStop, +} from 'preview/route/digitaltwins/execute/pipelineUtils'; +import { startPipelineStatusCheck } from 'preview/route/digitaltwins/execute/pipelineChecks'; +import { handleButtonClick, handleStart, handleStop, stopPipelines, setSnackbar } from 'preview/route/digitaltwins/execute/pipelineHandler'; + +jest.mock('util/gitlabDigitalTwin', () => ({ + ...jest.requireActual('util/gitlabDigitalTwin'), + formatName: jest.fn(), +})); + +jest.mock('preview/route/digitaltwins/execute/pipelineUtils', () => ({ + startPipeline: jest.fn(), + updatePipelineState: jest.fn(), + updatePipelineStateOnStop: jest.fn(), +})); + +jest.mock('preview/route/digitaltwins/execute/pipelineChecks', () => ({ + startPipelineStatusCheck: jest.fn(), +})); + +describe('handleButtonClick', () => { + let setButtonText: Dispatch>; + let setSnackbarMessage: Dispatch>; + let setSnackbarSeverity: Dispatch>; + let setSnackbarOpen: Dispatch>; + let setLogButtonDisabled: Dispatch>; + let dispatch: ReturnType; + const digitalTwin = {} as DigitalTwin; + + beforeEach(() => { + setButtonText = jest.fn(); + setSnackbarMessage = jest.fn(); + setSnackbarSeverity = jest.fn(); + setSnackbarOpen = jest.fn(); + setLogButtonDisabled = jest.fn(); + dispatch = jest.fn() as any; + }); + + it('should call handleStart when buttonText is "Start"', () => { + const handleStartSpy = jest.spyOn(require('preview/route/digitaltwins/execute/pipelineHandler'), 'handleStart'); + + handleButtonClick( + 'Start', + setButtonText, + digitalTwin, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + setLogButtonDisabled, + dispatch + ); + + expect(handleStartSpy).toHaveBeenCalledWith( + 'Start', + setButtonText, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + digitalTwin, + setLogButtonDisabled, + dispatch + ); + + handleStartSpy.mockRestore(); + }); + + it('should call handleStop when buttonText is not "Start"', () => { + const handleStopSpy = jest.spyOn(require('preview/route/digitaltwins/execute/pipelineHandler'), 'handleStop'); + + handleButtonClick( + 'Stop', + setButtonText, + digitalTwin, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + setLogButtonDisabled, + dispatch + ); + + expect(handleStopSpy).toHaveBeenCalledWith( + digitalTwin, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + setButtonText, + dispatch + ); + + handleStopSpy.mockRestore(); + }); + }); + + + + describe('handleStart', () => { + let setButtonText: Dispatch>; + let setSnackbarMessage: Dispatch>; + let setSnackbarSeverity: Dispatch>; + let setSnackbarOpen: Dispatch>; + let setLogButtonDisabled: Dispatch>; + let dispatch: ReturnType; + + const digitalTwin = {} as DigitalTwin; + + beforeEach(() => { + setButtonText = jest.fn(); + setSnackbarMessage = jest.fn(); + setSnackbarSeverity = jest.fn(); + setSnackbarOpen = jest.fn(); + setLogButtonDisabled = jest.fn(); + dispatch = jest.fn() as any; + }); + + it('should set button text to "Stop" and disable log button', async () => { + await handleStart( + 'Start', + setButtonText, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + digitalTwin, + setLogButtonDisabled, + dispatch + ); + + expect(setButtonText).toHaveBeenCalledWith('Stop'); + expect(setLogButtonDisabled).toHaveBeenCalledWith(true); + expect(updatePipelineState).toHaveBeenCalledWith(digitalTwin, dispatch); + expect(startPipelineStatusCheck).toHaveBeenCalled(); + }); + + it('should not change button text if it is not "Start"', async () => { + await handleStart( + 'Stop', + setButtonText, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + digitalTwin, + setLogButtonDisabled, + dispatch + ); + + expect(setButtonText).not.toHaveBeenCalledWith('Stop'); + }); + }); + + describe('handleStop', () => { + let setButtonText: Dispatch>; + let setSnackbarMessage: Dispatch>; + let setSnackbarSeverity: Dispatch>; + let setSnackbarOpen: Dispatch>; + let dispatch: ReturnType; + const digitalTwin = { + DTName: 'TestDTName', + gitlabInstance: { projectId: 1 }, + pipelineId: 1, + stop: jest.fn(), + } as unknown as DigitalTwin; + + beforeEach(() => { + setButtonText = jest.fn(); + setSnackbarMessage = jest.fn(); + setSnackbarSeverity = jest.fn(); + setSnackbarOpen = jest.fn(); + dispatch = jest.fn() as any; + }); + + it('should call stopPipelines and setSnackbar on success', async () => { + const stopPipelinesSpy = jest.spyOn(require('preview/route/digitaltwins/execute/pipelineHandler'), 'stopPipelines'); + stopPipelinesSpy.mockResolvedValueOnce(undefined); + + await handleStop( + digitalTwin, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen, + setButtonText, + dispatch + ); + + expect(stopPipelinesSpy).toHaveBeenCalledWith(digitalTwin); + expect(setSnackbarMessage).toHaveBeenCalledWith(`Execution stopped successfully for ${formatName(digitalTwin.DTName)}`); + expect(setSnackbarSeverity).toHaveBeenCalledWith('success'); + expect(setSnackbarOpen).toHaveBeenCalledWith(true); + expect(updatePipelineStateOnStop).toHaveBeenCalledWith(digitalTwin, setButtonText, dispatch); + expect(updatePipelineStateOnStop).toHaveBeenCalledWith(digitalTwin, setButtonText, dispatch); + }); + }); + + describe('stopPipelines', () => { + it('should call stop method twice with correct parameters', async () => { + const stopMock = jest.fn().mockResolvedValue(undefined); + const gitlabInstance = { projectId: 1 }; + const digitalTwin = { + gitlabInstance, + pipelineId: 1, + stop: stopMock, + } as unknown as DigitalTwin; + + // Call the stopPipelines function + await stopPipelines(digitalTwin); + + // Assert that stop was called with the correct parameters + expect(stopMock).toHaveBeenCalledTimes(2); + expect(stopMock).toHaveBeenNthCalledWith(1, 1, 1); + expect(stopMock).toHaveBeenNthCalledWith(2, 1, 2); + }); + }); + describe('setSnackbar', () => { + it('should set snackbar values correctly', () => { + const setSnackbarMessage = jest.fn(); + const setSnackbarSeverity = jest.fn(); + const setSnackbarOpen = jest.fn(); + + setSnackbar( + 'Test message', + 'success', + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen + ); + + expect(setSnackbarMessage).toHaveBeenCalledWith('Test message'); + expect(setSnackbarSeverity).toHaveBeenCalledWith('success'); + expect(setSnackbarOpen).toHaveBeenCalledWith(true); + }); + }); + \ No newline at end of file diff --git a/client/test/preview/unit/routes/digitaltwins/execute/PipelineUtils.test.ts b/client/test/preview/unit/routes/digitaltwins/execute/PipelineUtils.test.ts new file mode 100644 index 000000000..a3ca8e5e8 --- /dev/null +++ b/client/test/preview/unit/routes/digitaltwins/execute/PipelineUtils.test.ts @@ -0,0 +1,188 @@ +import { Dispatch, SetStateAction } from 'react'; +import { AlertColor } from '@mui/material'; +import DigitalTwin from 'util/gitlabDigitalTwin'; +import { GitlabInstance } from 'util/gitlab'; +import { useDispatch } from 'react-redux'; +import { + startPipeline, + updatePipelineState, + updatePipelineStateOnCompletion, + updatePipelineStateOnStop, + fetchJobLogs, +} from 'preview/route/digitaltwins/execute/pipelineUtils'; +import { + setJobLogs, + setPipelineCompleted, + setPipelineLoading, +} from 'store/digitalTwin.slice'; + +jest.mock('react-redux', () => ({ + useDispatch: jest.fn(), +})); + +jest.mock('store/digitalTwin.slice', () => ({ + setJobLogs: jest.fn(), + setPipelineCompleted: jest.fn(), + setPipelineLoading: jest.fn(), +})); + +describe('startPipeline', () => { + let digitalTwin: DigitalTwin; + let setSnackbarMessage: Dispatch>; + let setSnackbarSeverity: Dispatch>; + let setSnackbarOpen: Dispatch>; + + beforeEach(() => { + digitalTwin = { + DTName: 'TestDT', + lastExecutionStatus: 'success', + execute: jest.fn(), + } as unknown as DigitalTwin; + setSnackbarMessage = jest.fn(); + setSnackbarSeverity = jest.fn(); + setSnackbarOpen = jest.fn(); + }); + + it('should call execute and set success message if execution is successful', async () => { + await startPipeline( + digitalTwin, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen + ); + + expect(digitalTwin.execute).toHaveBeenCalled(); + expect(setSnackbarMessage).toHaveBeenCalledWith( + `Execution started successfully for TestDT. Wait until completion for the logs...` + ); + expect(setSnackbarSeverity).toHaveBeenCalledWith('success'); + expect(setSnackbarOpen).toHaveBeenCalledWith(true); + }); + + it('should set error message if execution fails', async () => { + digitalTwin.lastExecutionStatus = 'error'; + await startPipeline( + digitalTwin, + setSnackbarMessage, + setSnackbarSeverity, + setSnackbarOpen + ); + + expect(setSnackbarMessage).toHaveBeenCalledWith( + `Execution error for TestDT` + ); + expect(setSnackbarSeverity).toHaveBeenCalledWith('error'); + expect(setSnackbarOpen).toHaveBeenCalledWith(true); + }); +}); + +describe('updatePipelineState', () => { + let digitalTwin: DigitalTwin; + let dispatch: ReturnType; + + beforeEach(() => { + digitalTwin = { DTName: 'TestDT' } as DigitalTwin; + dispatch = jest.fn(); + }); + + it('should dispatch setPipelineCompleted and setPipelineLoading actions', () => { + updatePipelineState(digitalTwin, dispatch); + + expect(dispatch).toHaveBeenCalledWith( + setPipelineCompleted({ assetName: 'TestDT', pipelineCompleted: false }) + ); + expect(dispatch).toHaveBeenCalledWith( + setPipelineLoading({ assetName: 'TestDT', pipelineLoading: true }) + ); + }); +}); + +describe('updatePipelineStateOnCompletion', () => { + let digitalTwin: DigitalTwin; + let dispatch: ReturnType; + let setButtonText: Dispatch>; + let setLogButtonDisabled: Dispatch>; + let jobLogs: { jobName: string; log: string }[]; + + beforeEach(() => { + digitalTwin = { DTName: 'TestDT' } as DigitalTwin; + dispatch = jest.fn(); + setButtonText = jest.fn(); + setLogButtonDisabled = jest.fn(); + jobLogs = [{ jobName: 'TestJob', log: 'Test log' }]; + }); + + it('should dispatch actions and reset button states on completion', () => { + updatePipelineStateOnCompletion( + digitalTwin, + jobLogs, + setButtonText, + setLogButtonDisabled, + dispatch + ); + + expect(dispatch).toHaveBeenCalledWith( + setJobLogs({ assetName: 'TestDT', jobLogs }) + ); + expect(dispatch).toHaveBeenCalledWith( + setPipelineCompleted({ assetName: 'TestDT', pipelineCompleted: true }) + ); + expect(dispatch).toHaveBeenCalledWith( + setPipelineLoading({ assetName: 'TestDT', pipelineLoading: false }) + ); + expect(setButtonText).toHaveBeenCalledWith('Start'); + expect(setLogButtonDisabled).toHaveBeenCalledWith(false); + }); +}); + +describe('updatePipelineStateOnStop', () => { + let digitalTwin: DigitalTwin; + let dispatch: ReturnType; + let setButtonText: Dispatch>; + + beforeEach(() => { + digitalTwin = { DTName: 'TestDT' } as DigitalTwin; + dispatch = jest.fn(); + setButtonText = jest.fn(); + }); + + it('should dispatch actions and reset button state on stop', () => { + updatePipelineStateOnStop(digitalTwin, setButtonText, dispatch); + + expect(setButtonText).toHaveBeenCalledWith('Start'); + expect(dispatch).toHaveBeenCalledWith( + setPipelineCompleted({ assetName: 'TestDT', pipelineCompleted: true }) + ); + expect(dispatch).toHaveBeenCalledWith( + setPipelineLoading({ assetName: 'TestDT', pipelineLoading: false }) + ); + }); +}); + +describe('fetchJobLogs', () => { + let gitlabInstance: GitlabInstance; + let pipelineId: number; + + beforeEach(() => { + gitlabInstance = { + projectId: 1, + getPipelineJobs: jest.fn().mockResolvedValue([ + { id: 1, name: 'Job1' }, + { id: 2, name: 'Job2' }, + ]), + getJobTrace: jest.fn().mockResolvedValue('Job log'), + } as unknown as GitlabInstance; + pipelineId = 1; + }); + + it('should fetch job logs and return parsed logs', async () => { + const logs = await fetchJobLogs(gitlabInstance, pipelineId); + + expect(gitlabInstance.getPipelineJobs).toHaveBeenCalledWith(1, pipelineId); + expect(gitlabInstance.getJobTrace).toHaveBeenCalledTimes(2); + expect(logs).toEqual([ + { jobName: 'Job2', log: 'Job log' }, + { jobName: 'Job1', log: 'Job log' }, + ]); + }); +}); diff --git a/client/test/unit/components/asset/AssetCard.test.tsx b/client/test/unit/components/asset/AssetCard.test.tsx deleted file mode 100644 index e9b4f3b48..000000000 --- a/client/test/unit/components/asset/AssetCard.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from 'react'; -import { render, screen } from '@testing-library/react'; -import AssetCardExecute from 'components/asset/AssetCard'; -import { Provider } from 'react-redux'; -import store from 'store/store'; -import '@testing-library/jest-dom'; - -if (!AbortSignal.timeout) { - AbortSignal.timeout = (ms) => { - const controller = new AbortController(); - setTimeout(() => controller.abort(new DOMException('TimeoutError')), ms); - return controller.signal; - }; -} - -jest.deepUnmock('components/asset/AssetCard'); - -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), -})); - -jest.mock('react-oidc-context', () => ({ - ...jest.requireActual('react-oidc-context'), - useAuth: jest.fn(), -})); - -jest.mock('util/envUtil', () => ({ - ...jest.requireActual('util/envUtil'), - getAuthority: jest.fn(() => 'https://example.com'), -})); - -jest.mock(''); - -describe('AssetCard', () => { - const assetMock = { - name: 'TestName', - path: 'testPath', - description: 'testDescription', - }; - - beforeEach(() => { - render( - - - ); - , - ); - }); - - it('renders asset name correctly', () => { - expect(screen.getByText(assetMock.name)).toBeInTheDocument(); - }); -}); diff --git a/client/test/unit/routes/digitaltwins/ExecutionFunctions.test.ts b/client/test/unit/routes/digitaltwins/ExecutionFunctions.test.ts deleted file mode 100644 index 8e1a09725..000000000 --- a/client/test/unit/routes/digitaltwins/ExecutionFunctions.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { - handleButtonClick, - handleStop, - checkFirstPipelineStatus, - fetchJobLogs, - handlePipelineCompletion, - retryPipelineCheck, -} from 'route/digitaltwins/ExecutionFunctions'; -import DigitalTwin from 'util/gitlabDigitalTwin'; -import { GitlabInstance } from 'util/gitlab'; - -jest.mock('util/gitlabDigitalTwin'); -jest.mock('util/gitlab', () => ({ - GitlabInstance: jest.fn().mockImplementation(() => ({ - getPipelineStatus: jest.fn(), - getPipelineJobs: jest.fn(), - getJobTrace: jest.fn(), - stop: jest.fn(), - })), -})); - -jest.mock('strip-ansi'); - -describe('ExecutionFunctions', () => { - let mockSetButtonText: jest.Mock; - let mockSetJobLogs: jest.Mock; - let mockSetPipelineCompleted: jest.Mock; - let mockSetPipelineLoading: jest.Mock; - let mockSetExecutionStatus: jest.Mock; - let mockSetSnackbarMessage: jest.Mock; - let mockSetSnackbarSeverity: jest.Mock; - let mockSetSnackbarOpen: jest.Mock; - let digitalTwin: DigitalTwin; - let gitlabInstance: GitlabInstance; - - beforeEach(() => { - mockSetButtonText = jest.fn(); - mockSetJobLogs = jest.fn(); - mockSetPipelineCompleted = jest.fn(); - mockSetPipelineLoading = jest.fn(); - mockSetExecutionStatus = jest.fn(); - mockSetSnackbarMessage = jest.fn(); - mockSetSnackbarSeverity = jest.fn(); - mockSetSnackbarOpen = jest.fn(); - gitlabInstance = new GitlabInstance('user1', 'authority', 'token1'); - digitalTwin = new DigitalTwin('DTName', gitlabInstance); - }); - - it('should handle button click with Start text', async () => { - await handleButtonClick( - 'Start', - mockSetButtonText, - mockSetJobLogs, - mockSetPipelineCompleted, - mockSetPipelineLoading, - mockSetExecutionStatus, - digitalTwin, - mockSetSnackbarMessage, - mockSetSnackbarSeverity, - mockSetSnackbarOpen, - 0, - ); - - expect(mockSetButtonText).toHaveBeenCalledWith('Stop'); - expect(mockSetJobLogs).toHaveBeenCalledWith([]); - }); - - it('should call handleStop when buttonText is not "Start"', async () => { - digitalTwin.stop = jest.fn(); - - await handleButtonClick( - 'Stop', - mockSetButtonText, - mockSetJobLogs, - mockSetPipelineCompleted, - mockSetPipelineLoading, - mockSetExecutionStatus, - digitalTwin, - mockSetSnackbarMessage, - mockSetSnackbarSeverity, - mockSetSnackbarOpen, - 1, - ); - - expect(mockSetButtonText).toHaveBeenCalledWith('Start'); - }); - - it('should check first pipeline status', async () => { - gitlabInstance.projectId = 1; - gitlabInstance.getPipelineStatus = jest.fn().mockResolvedValue('success'); - await checkFirstPipelineStatus( - gitlabInstance, - 1, - mockSetJobLogs, - mockSetPipelineCompleted, - mockSetPipelineLoading, - mockSetButtonText, - ); - - expect(gitlabInstance.getPipelineStatus).toHaveBeenCalledWith('user1', 1); - expect(mockSetPipelineCompleted).toHaveBeenCalledWith(true); - }); - - it('should fetch job logs', async () => { - gitlabInstance.getPipelineJobs = jest - .fn() - .mockResolvedValue([{ id: 1, name: 'Job1' }]); - gitlabInstance.getJobTrace = jest.fn().mockResolvedValue('log content'); - - const logs = await fetchJobLogs(gitlabInstance, 1); - - expect(gitlabInstance.getPipelineJobs).toHaveBeenCalledWith('user1', 1); - expect(gitlabInstance.getJobTrace).toHaveBeenCalledWith('user1', 1); - expect(logs).toEqual([{ jobName: 'Job1', log: 'log content' }]); - }); - - it('should handle pipeline completion', async () => { - gitlabInstance.getPipelineStatus = jest.fn().mockResolvedValue('success'); - await handlePipelineCompletion( - gitlabInstance, - 2, - mockSetJobLogs, - mockSetPipelineCompleted, - mockSetPipelineLoading, - mockSetButtonText, - ); - - expect(gitlabInstance.getPipelineStatus).toHaveBeenCalled(); - }); - - it('should retry pipeline check', () => { - jest.useFakeTimers(); - retryPipelineCheck( - gitlabInstance, - 1, - mockSetJobLogs, - mockSetPipelineCompleted, - mockSetPipelineLoading, - mockSetButtonText, - ); - jest.advanceTimersByTime(5000); - - expect(gitlabInstance.getPipelineStatus).toHaveBeenCalled(); - }); - - it('should handle stop execution with success', async () => { - digitalTwin.stop = jest.fn().mockResolvedValue(true); - - await handleStop( - digitalTwin, - mockSetSnackbarMessage, - mockSetSnackbarSeverity, - mockSetSnackbarOpen, - 0, - mockSetButtonText, - mockSetPipelineCompleted, - mockSetPipelineLoading, - ); - - expect(mockSetSnackbarMessage).toHaveBeenCalledWith( - 'DTName (Run #0) execution stopped successfully', - ); - expect(mockSetSnackbarSeverity).toHaveBeenCalledWith('success'); - expect(mockSetButtonText).toHaveBeenCalledWith('Start'); - }); -}); diff --git a/client/test/unit/util/Store.test.ts b/client/test/unit/util/Store.test.ts index dd638a44c..79b73190c 100644 --- a/client/test/unit/util/Store.test.ts +++ b/client/test/unit/util/Store.test.ts @@ -1,5 +1,9 @@ import menuReducer, { openMenu, closeMenu } from 'store/menu.slice'; import authReducer, { setUserName } from 'store/auth.slice'; +import digitalTwinReducer, { setDigitalTwin, setJobLogs, setPipelineCompleted, setPipelineLoading } from 'store/digitalTwin.slice'; +import DigitalTwin from 'util/gitlabDigitalTwin'; +import { GitlabInstance } from 'util/gitlab'; +import { JobLog } from 'preview/components/asset/StartStopButton'; describe('reducers', () => { let initialState: { @@ -9,6 +13,9 @@ describe('reducers', () => { auth: { userName: string | undefined; }; + digitalTwin: { + [key: string]: DigitalTwin; + }; }; beforeEach(() => { @@ -19,6 +26,7 @@ describe('reducers', () => { auth: { userName: undefined, }, + digitalTwin: {}, }; }); @@ -48,7 +56,7 @@ describe('reducers', () => { }); describe('auth reducer', () => { - it('authReducer should return the initial menu state when an unknown action type is passed with an undefined state', () => { + it('authReducer should return the initial auth state when an unknown action type is passed with an undefined state', () => { expect(authReducer(undefined, { type: 'unknown' })).toEqual( initialState.auth, ); @@ -59,4 +67,41 @@ describe('reducers', () => { expect(newState.userName).toBe('user1'); }); }); + + describe('digitalTwin reducer', () => { + it('digitalTwinReducer should return the initial digitalTwin state when an unknown action type is passed with an undefined state', () => { + expect(digitalTwinReducer(undefined, { type: 'unknown' })).toEqual( + initialState.digitalTwin, + ); + }); + + it('should handle setDigitalTwin', () => { + const digitalTwin = new DigitalTwin('asset1', new GitlabInstance('user1', 'authority', 'token1')); + const newState = digitalTwinReducer(initialState.digitalTwin, setDigitalTwin({ assetName: 'asset1', digitalTwin })); + expect(newState['asset1']).toEqual(digitalTwin); + }); + + it('should handle setJobLogs', () => { + const jobLogs: JobLog[] = [{ jobName: 'job1', log: 'log'}]; + const digitalTwin = new DigitalTwin('asset1', new GitlabInstance('user1', 'authority', 'token1')); + digitalTwin.jobLogs = jobLogs; + initialState.digitalTwin['asset1'] = digitalTwin; + const newState = digitalTwinReducer(initialState.digitalTwin, setJobLogs({ assetName: 'asset1', jobLogs })); + expect(newState['asset1'].jobLogs).toEqual(jobLogs); + }); + + it('should handle setPipelineCompleted', () => { + const digitalTwin = new DigitalTwin('asset1', new GitlabInstance('user1', 'authority', 'token1')); + initialState.digitalTwin['asset1'] = digitalTwin; + const newState = digitalTwinReducer(initialState.digitalTwin, setPipelineCompleted({ assetName: 'asset1', pipelineCompleted: true })); + expect(newState['asset1'].pipelineCompleted).toBe(true); + }); + + it('should handle setPipelineLoading', () => { + const digitalTwin = new DigitalTwin('asset1', new GitlabInstance('user1', 'authority', 'token1')); + initialState.digitalTwin['asset1'] = digitalTwin; + const newState = digitalTwinReducer(initialState.digitalTwin, setPipelineLoading({ assetName: 'asset1', pipelineLoading: true })); + expect(newState['asset1'].pipelineLoading).toBe(true); + }); + }); }); diff --git a/client/test/unit/util/envUtil.test.ts b/client/test/unit/util/envUtil.test.ts index 883b08af4..8ead1ccc5 100644 --- a/client/test/unit/util/envUtil.test.ts +++ b/client/test/unit/util/envUtil.test.ts @@ -32,6 +32,7 @@ describe('envUtil', () => { REACT_APP_WORKBENCHLINK_VSCODE: testWorkbenchEndpoints[1], REACT_APP_WORKBENCHLINK_JUPYTERLAB: testWorkbenchEndpoints[2], REACT_APP_WORKBENCHLINK_JUPYTERNOTEBOOK: testWorkbenchEndpoints[3], + REACT_APP_WORKBENCHLINK_DT_PREVIEW: testWorkbenchEndpoints[4], REACT_APP_CLIENT_ID: testAppID, REACT_APP_AUTH_AUTHORITY: testAuthority, diff --git a/client/yarn.lock b/client/yarn.lock index bf9121c9a..8c89526de 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2300,15 +2300,15 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" -"@testing-library/dom@^9.0.0", "@testing-library/dom@^9.3.3": - version "9.3.4" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce" - integrity sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ== +"@testing-library/dom@^10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8" + integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" "@types/aria-query" "^5.0.1" - aria-query "5.1.3" + aria-query "5.3.0" chalk "^4.1.0" dom-accessibility-api "^0.5.9" lz-string "^1.5.0" @@ -2327,14 +2327,12 @@ lodash "^4.17.21" redent "^3.0.0" -"@testing-library/react@^14.1.2": - version "14.3.1" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.3.1.tgz#29513fc3770d6fb75245c4e1245c470e4ffdd830" - integrity sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ== +"@testing-library/react@^16.0.1": + version "16.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.1.tgz#29c0ee878d672703f5e7579f239005e4e0faa875" + integrity sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^9.0.0" - "@types/react-dom" "^18.0.0" "@testing-library/user-event@^14.5.1": version "14.5.2" @@ -2589,7 +2587,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.17": +"@types/react-dom@^18.3.0": version "18.3.0" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== @@ -2603,7 +2601,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.3.3": +"@types/react@*", "@types/react@^18.3.5": version "18.3.5" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.5.tgz#5f524c2ad2089c0ff372bbdabc77ca2c4dbadf8f" integrity sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA== @@ -3260,20 +3258,20 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@5.1.3, aria-query@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" - integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== - dependencies: - deep-equal "^2.0.5" - -aria-query@^5.0.0: +aria-query@5.3.0, aria-query@^5.0.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== dependencies: dequal "^2.0.3" +aria-query@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== + dependencies: + deep-equal "^2.0.5" + array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" @@ -10248,7 +10246,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10351,7 +10358,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11513,7 +11527,16 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==