From 0a8541320dde6cb91709eac345d43dfa9e133a60 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Tue, 24 Sep 2024 13:41:17 -0700 Subject: [PATCH 01/14] backend changes --- express-api/src/controllers/users/usersController.ts | 1 + express-api/src/services/users/usersServices.ts | 5 +++++ express-api/src/utilities/helperFunctions.ts | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/express-api/src/controllers/users/usersController.ts b/express-api/src/controllers/users/usersController.ts index b8bbc82f8..c6f7f512d 100644 --- a/express-api/src/controllers/users/usersController.ts +++ b/express-api/src/controllers/users/usersController.ts @@ -20,6 +20,7 @@ export const submitUserAccessRequest = async (req: Request, res: Response) => { Number(req.body.AgencyId), req.body.Position, req.body.Note, + req.body.Email, ); const config = getConfig(); const user = await userServices.getUser(req.user.preferred_username); diff --git a/express-api/src/services/users/usersServices.ts b/express-api/src/services/users/usersServices.ts index 142328993..58ca09aa4 100644 --- a/express-api/src/services/users/usersServices.ts +++ b/express-api/src/services/users/usersServices.ts @@ -6,6 +6,7 @@ import { Agency } from '@/typeorm/Entities/Agency'; import { randomUUID, UUID } from 'crypto'; import { ErrorWithCode } from '@/utilities/customErrors/ErrorWithCode'; import { UserFiltering } from '@/controllers/users/usersSchema'; +import { validateEmail } from '@/utilities/helperFunctions'; interface NormalizedKeycloakUser { first_name: string; @@ -54,10 +55,14 @@ const addKeycloakUserOnHold = async ( agencyId: number, position: string, note: string, + email: string, ) => { if (agencyId == null) { throw new Error('Null argument.'); } + if (!validateEmail(email)) { + throw new Error('Invalid email.'); + } //Iterating through agencies and roles no longer necessary here? const normalizedKc = normalizeKeycloakUser(ssoUser); const systemUser = await AppDataSource.getRepository(User).findOne({ diff --git a/express-api/src/utilities/helperFunctions.ts b/express-api/src/utilities/helperFunctions.ts index d9abe7773..e2c22bbbc 100644 --- a/express-api/src/utilities/helperFunctions.ts +++ b/express-api/src/utilities/helperFunctions.ts @@ -1,4 +1,5 @@ import { Equal, FindOptionsWhere, IsNull, Not, Raw } from 'typeorm'; +import { z } from 'zod'; /** * Special case for PID/PIN matching, as general text comparison is not sufficient. @@ -244,3 +245,6 @@ export const toPostgresTimestamp = (date: Date) => { export const getDaysBetween = (earlierDate: Date, laterDate: Date): number => { return Math.trunc((laterDate.getTime() - earlierDate.getTime()) / (1000 * 60 * 60 * 24)); }; + +export const validateEmail = (email: string): boolean => + z.string().email().safeParse(email).success; From 14ce996a1930cb116b25d43c72108f5295c6778a Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Tue, 24 Sep 2024 13:54:44 -0700 Subject: [PATCH 02/14] react-app email change --- react-app/package.json | 3 ++- react-app/src/components/users/UserDetail.tsx | 12 ++++++++- react-app/src/pages/AccessRequest.tsx | 27 ++++++++++++++----- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/react-app/package.json b/react-app/package.json index eeba862a3..dd50025d1 100644 --- a/react-app/package.json +++ b/react-app/package.json @@ -37,7 +37,8 @@ "react-router-dom": "6.26.0", "supercluster": "8.0.1", "typescript-eslint": "8.6.0", - "use-supercluster": "1.2.0" + "use-supercluster": "1.2.0", + "zod": "3.23.8" }, "devDependencies": { "@babel/preset-env": "7.25.2", diff --git a/react-app/src/components/users/UserDetail.tsx b/react-app/src/components/users/UserDetail.tsx index d1bc9be10..c4d256389 100644 --- a/react-app/src/components/users/UserDetail.tsx +++ b/react-app/src/components/users/UserDetail.tsx @@ -18,6 +18,7 @@ import useDataSubmitter from '@/hooks/useDataSubmitter'; import { Role, Roles } from '@/constants/roles'; import { LookupContext } from '@/contexts/lookupContext'; import { getProvider } from '@/utilities/helperFunctions'; +import { z } from 'zod'; interface IUserDetail { onClose: () => void; @@ -179,7 +180,16 @@ const UserDetail = ({ onClose }: IUserDetail) => { - + + z.string().email().safeParse(value).success || 'Invalid email.', + }} + /> diff --git a/react-app/src/pages/AccessRequest.tsx b/react-app/src/pages/AccessRequest.tsx index 8678c3e24..3856ed901 100644 --- a/react-app/src/pages/AccessRequest.tsx +++ b/react-app/src/pages/AccessRequest.tsx @@ -19,6 +19,7 @@ import { useGroupedAgenciesApi } from '@/hooks/api/useGroupedAgenciesApi'; import { SnackBarContext } from '@/contexts/snackbarContext'; import { LookupContext } from '@/contexts/lookupContext'; import { getProvider } from '@/utilities/helperFunctions'; +import z from 'zod'; interface StatusPageTemplateProps { blurb: JSX.Element; @@ -49,12 +50,14 @@ const RequestForm = ({ submitHandler }: { submitHandler: (d: any) => void }) => [keycloak.user, lookup], ); + const userIsIdir = provider === 'IDIR'; + const formMethods = useForm({ defaultValues: { Provider: provider, - FirstName: keycloak.user?.first_name, - LastName: keycloak.user?.last_name, - Email: keycloak.user?.email, + FirstName: keycloak.user?.first_name || '', + LastName: keycloak.user?.last_name || '', + Email: userIsIdir ? keycloak.user?.email : '', Notes: '', Agency: '', Position: '', @@ -66,7 +69,7 @@ const RequestForm = ({ submitHandler }: { submitHandler: (d: any) => void }) => Provider: provider, FirstName: keycloak.user?.first_name || '', LastName: keycloak.user?.last_name || '', - Email: keycloak.user?.email || '', + Email: userIsIdir ? keycloak.user?.email : '', Notes: '', Agency: '', Position: '', @@ -81,13 +84,23 @@ const RequestForm = ({ submitHandler }: { submitHandler: (d: any) => void }) => - + + z.string().email().safeParse(value).success || 'Invalid email.', + }} + /> - + - + Date: Tue, 24 Sep 2024 13:57:28 -0700 Subject: [PATCH 03/14] combine to one helper function --- react-app/src/components/users/UserDetail.tsx | 6 ++---- react-app/src/pages/AccessRequest.tsx | 6 ++---- react-app/src/utilities/helperFunctions.ts | 5 +++++ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/react-app/src/components/users/UserDetail.tsx b/react-app/src/components/users/UserDetail.tsx index c4d256389..b932e794c 100644 --- a/react-app/src/components/users/UserDetail.tsx +++ b/react-app/src/components/users/UserDetail.tsx @@ -17,8 +17,7 @@ import { useParams } from 'react-router-dom'; import useDataSubmitter from '@/hooks/useDataSubmitter'; import { Role, Roles } from '@/constants/roles'; import { LookupContext } from '@/contexts/lookupContext'; -import { getProvider } from '@/utilities/helperFunctions'; -import { z } from 'zod'; +import { getProvider, validateEmail } from '@/utilities/helperFunctions'; interface IUserDetail { onClose: () => void; @@ -186,8 +185,7 @@ const UserDetail = ({ onClose }: IUserDetail) => { name={'Email'} label={'Email'} rules={{ - validate: (value: string) => - z.string().email().safeParse(value).success || 'Invalid email.', + validate: (value: string) => validateEmail(value) || 'Invalid email.', }} /> diff --git a/react-app/src/pages/AccessRequest.tsx b/react-app/src/pages/AccessRequest.tsx index 3856ed901..6da943847 100644 --- a/react-app/src/pages/AccessRequest.tsx +++ b/react-app/src/pages/AccessRequest.tsx @@ -18,8 +18,7 @@ import TextFormField from '@/components/form/TextFormField'; import { useGroupedAgenciesApi } from '@/hooks/api/useGroupedAgenciesApi'; import { SnackBarContext } from '@/contexts/snackbarContext'; import { LookupContext } from '@/contexts/lookupContext'; -import { getProvider } from '@/utilities/helperFunctions'; -import z from 'zod'; +import { getProvider, validateEmail } from '@/utilities/helperFunctions'; interface StatusPageTemplateProps { blurb: JSX.Element; @@ -91,8 +90,7 @@ const RequestForm = ({ submitHandler }: { submitHandler: (d: any) => void }) => disabled={userIsIdir} required rules={{ - validate: (value: string) => - z.string().email().safeParse(value).success || 'Invalid email.', + validate: (value: string) => validateEmail(value) || 'Invalid email.', }} /> diff --git a/react-app/src/utilities/helperFunctions.ts b/react-app/src/utilities/helperFunctions.ts index d56adbfd0..2877f1f45 100644 --- a/react-app/src/utilities/helperFunctions.ts +++ b/react-app/src/utilities/helperFunctions.ts @@ -1,3 +1,5 @@ +import { z } from 'zod'; + /** * Pass an array of some arbitrary type and a function that retrieves the key determining uniqueness. * Returns an new array with any duplicate values omitted. @@ -71,3 +73,6 @@ export const getProvider = (username: string, bcscIdentifier?: string) => { return ''; } }; + +export const validateEmail = (email: string): boolean => + z.string().email().safeParse(email).success; From 7f9979574789978fb19c152ae15af39087398903 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Tue, 24 Sep 2024 14:00:20 -0700 Subject: [PATCH 04/14] update tests --- .../tests/unit/services/users/usersServices.test.ts | 8 +++++++- .../tests/unit/utilities/helperFunctions.test.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/express-api/tests/unit/services/users/usersServices.test.ts b/express-api/tests/unit/services/users/usersServices.test.ts index 0f81f83b1..4d2cadfd0 100644 --- a/express-api/tests/unit/services/users/usersServices.test.ts +++ b/express-api/tests/unit/services/users/usersServices.test.ts @@ -108,7 +108,13 @@ describe('UNIT - User services', () => { it('should add and return an access request', async () => { const agencyId = faker.number.int(); //const roleId = faker.string.uuid(); - const req = await userServices.addKeycloakUserOnHold(ssoUser, agencyId, '', ''); + const req = await userServices.addKeycloakUserOnHold( + ssoUser, + agencyId, + '', + '', + 'email@email.com', + ); expect(_usersInsert).toHaveBeenCalledTimes(1); }); }); diff --git a/express-api/tests/unit/utilities/helperFunctions.test.ts b/express-api/tests/unit/utilities/helperFunctions.test.ts index 7aba1a50a..d861c9964 100644 --- a/express-api/tests/unit/utilities/helperFunctions.test.ts +++ b/express-api/tests/unit/utilities/helperFunctions.test.ts @@ -5,6 +5,7 @@ import { ILikeWrapper, TimestampComparisonWrapper, toPostgresTimestamp, + validateEmail, } from '@/utilities/helperFunctions'; import { EqualOperator, FindOperator } from 'typeorm'; @@ -180,4 +181,16 @@ describe('UNIT - helperFunctions', () => { expect(result.test).toBeUndefined(); }); }); + + describe('validateEmail', () => { + it('should return true when a valid email is given', () => { + const result = validateEmail('test@gmail.com'); + expect(result).toEqual(true); + }); + + it('should return false when a invalid email is given', () => { + const result = validateEmail('test@gmaom'); + expect(result).toEqual(false); + }); + }); }); From ac702ee870865d4ad2ad677dec9da39b7b796ff4 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Tue, 24 Sep 2024 14:08:26 -0700 Subject: [PATCH 05/14] add validation checks to update and add --- express-api/src/services/users/usersServices.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/express-api/src/services/users/usersServices.ts b/express-api/src/services/users/usersServices.ts index 58ca09aa4..da7cb6b88 100644 --- a/express-api/src/services/users/usersServices.ts +++ b/express-api/src/services/users/usersServices.ts @@ -212,6 +212,9 @@ const addUser = async (user: User) => { if (resource) { throw new ErrorWithCode('Resource already exists.', 409); } + if (!validateEmail(user.Email)) { + throw new Error('Invalid email.'); + } const retUser = await AppDataSource.getRepository(User).save(user); return retUser; }; @@ -227,6 +230,9 @@ const updateUser = async (user: DeepPartial) => { if (!resource) { throw new ErrorWithCode('Resource does not exist.', 404); } + if (user.Email && !validateEmail(user.Email)) { + throw new Error('Invalid email.'); + } await AppDataSource.getRepository(User).update(user.Id, { ...user, DisplayName: `${user.LastName}, ${user.FirstName}`, From c17f14c697d89f450866b8e77769684ae184323d Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Tue, 24 Sep 2024 14:27:39 -0700 Subject: [PATCH 06/14] add tooltip to access request email --- react-app/src/pages/AccessRequest.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/react-app/src/pages/AccessRequest.tsx b/react-app/src/pages/AccessRequest.tsx index 6da943847..801f751eb 100644 --- a/react-app/src/pages/AccessRequest.tsx +++ b/react-app/src/pages/AccessRequest.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useMemo } from 'react'; import pendingImage from '@/assets/images/pending.svg'; -import { Box, Button, Grid, Paper, Typography } from '@mui/material'; +import { Box, Button, Grid, Paper, Tooltip, Typography, useTheme } from '@mui/material'; import AutocompleteFormField from '@/components/form/AutocompleteFormField'; import { useSSO } from '@bcgov/citz-imb-sso-react'; import { FormProvider, useForm } from 'react-hook-form'; @@ -19,6 +19,7 @@ import { useGroupedAgenciesApi } from '@/hooks/api/useGroupedAgenciesApi'; import { SnackBarContext } from '@/contexts/snackbarContext'; import { LookupContext } from '@/contexts/lookupContext'; import { getProvider, validateEmail } from '@/utilities/helperFunctions'; +import InfoIcon from '@mui/icons-material/Info'; interface StatusPageTemplateProps { blurb: JSX.Element; @@ -43,6 +44,7 @@ const RequestForm = ({ submitHandler }: { submitHandler: (d: any) => void }) => const keycloak = useSSO(); const agencyOptions = useGroupedAgenciesApi().agencyOptions; const lookup = useContext(LookupContext); + const theme = useTheme(); const provider = useMemo( () => getProvider(keycloak.user?.preferred_username, lookup?.data?.Config.bcscIdentifier), @@ -92,6 +94,15 @@ const RequestForm = ({ submitHandler }: { submitHandler: (d: any) => void }) => rules={{ validate: (value: string) => validateEmail(value) || 'Invalid email.', }} + slotProps={{ + input: { + endAdornment: ( + + + + ), + }, + }} /> From 1da671945d1e6db3360e6653c1398aefab60a498 Mon Sep 17 00:00:00 2001 From: taylorfries Date: Thu, 26 Sep 2024 09:02:18 -0700 Subject: [PATCH 07/14] update email from kc user to entered email --- express-api/src/services/users/usersServices.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/express-api/src/services/users/usersServices.ts b/express-api/src/services/users/usersServices.ts index da7cb6b88..0079f9193 100644 --- a/express-api/src/services/users/usersServices.ts +++ b/express-api/src/services/users/usersServices.ts @@ -48,6 +48,7 @@ const normalizeKeycloakUser = (kcUser: SSOUser): NormalizedKeycloakUser => { * @param agencyId The ID of the agency the user belongs to * @param position The position of the user * @param note Additional notes about the user + * @param the users email * @returns The inserted user */ const addKeycloakUserOnHold = async ( @@ -73,7 +74,7 @@ const addKeycloakUserOnHold = async ( Id: id, FirstName: normalizedKc.first_name, LastName: normalizedKc.last_name, - Email: normalizedKc.email, + Email: email, DisplayName: normalizedKc.display_name, KeycloakUserId: normalizedKc.guid, Username: normalizedKc.username, From a7453227d5d196f3541128e1700cd77e967f8390 Mon Sep 17 00:00:00 2001 From: taylorfries Date: Thu, 26 Sep 2024 10:50:44 -0700 Subject: [PATCH 08/14] did a bad merge --- react-app/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-app/package.json b/react-app/package.json index 14e54b098..401070c9e 100644 --- a/react-app/package.json +++ b/react-app/package.json @@ -37,8 +37,8 @@ "react-router-dom": "6.26.0", "supercluster": "8.0.1", "use-supercluster": "1.2.0", - "zod": "3.23.8" - "typescript-eslint": "8.7.0", + "zod": "3.23.8", + "typescript-eslint": "8.7.0" }, "devDependencies": { "@babel/preset-env": "7.25.2", From 585fc18d034d25bf36bbf6dea8861e1cf5d96439 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 2 Oct 2024 08:21:48 -0700 Subject: [PATCH 09/14] touch up comments --- express-api/src/services/users/usersServices.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/express-api/src/services/users/usersServices.ts b/express-api/src/services/users/usersServices.ts index 0079f9193..15ef574a7 100644 --- a/express-api/src/services/users/usersServices.ts +++ b/express-api/src/services/users/usersServices.ts @@ -44,11 +44,11 @@ const normalizeKeycloakUser = (kcUser: SSOUser): NormalizedKeycloakUser => { /** * Adds a user from Keycloak to the system with 'OnHold' status. - * @param ssoUser The Keycloak user to be added - * @param agencyId The ID of the agency the user belongs to - * @param position The position of the user - * @param note Additional notes about the user - * @param the users email + * @param {SSOUser} ssoUser The Keycloak user to be added + * @param {number} agencyId The ID of the agency the user belongs to + * @param {string} position The position of the user + * @param {string} note Additional notes about the user + * @param {string} email the users email * @returns The inserted user */ const addKeycloakUserOnHold = async ( From 7a5aa3e65d089730d29f16587fb5021528121839 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 2 Oct 2024 08:58:32 -0700 Subject: [PATCH 10/14] add more userServices tests --- .../unit/services/users/usersServices.test.ts | 76 +++++++++++++++++-- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/express-api/tests/unit/services/users/usersServices.test.ts b/express-api/tests/unit/services/users/usersServices.test.ts index 4d2cadfd0..95c6c61a0 100644 --- a/express-api/tests/unit/services/users/usersServices.test.ts +++ b/express-api/tests/unit/services/users/usersServices.test.ts @@ -74,7 +74,7 @@ jest .spyOn(AppDataSource.getRepository(Agency), 'findOneOrFail') .mockImplementation(async () => produceAgency()); -jest +const _agenciesFind = jest .spyOn(AppDataSource.getRepository(Agency), 'find') .mockImplementation(async () => [produceAgency()]); @@ -104,10 +104,24 @@ describe('UNIT - User services', () => { }); }); - describe('addAccessRequest', () => { + describe('getUserById', () => { + it('should return a user assuming one is found', async () => { + const user = produceUser(); + _usersFindOne.mockImplementationOnce(async () => user); + const result = await userServices.getUserById('123'); + expect(result).toEqual(user); + }); + + it('should return null when no user is found', async () => { + _usersFindOne.mockImplementationOnce(async () => null); + const result = await userServices.getUserById('123'); + expect(result).toEqual(null); + }); + }); + + describe('addKeycloakUserOnHold', () => { it('should add and return an access request', async () => { const agencyId = faker.number.int(); - //const roleId = faker.string.uuid(); const req = await userServices.addKeycloakUserOnHold( ssoUser, agencyId, @@ -117,15 +131,50 @@ describe('UNIT - User services', () => { ); expect(_usersInsert).toHaveBeenCalledTimes(1); }); + + it('should throw an error if the agency is null', () => { + expect( + async () => + await userServices.addKeycloakUserOnHold(ssoUser, null, '', '', 'email@email.com'), + ).rejects.toThrow('Null argument.'); + }); + + it('should throw an error if the email is invalid', () => { + const agencyId = faker.number.int(); + expect( + async () => + await userServices.addKeycloakUserOnHold(ssoUser, agencyId, '', '', 'email.com'), + ).rejects.toThrow('Invalid email.'); + }); }); describe('getAgencies', () => { it('should return an array of agency ids', async () => { const agencies = await userServices.getAgencies('test'); - expect(AppDataSource.getRepository(User).findOneOrFail).toHaveBeenCalledTimes(1); expect(AppDataSource.getRepository(Agency).find).toHaveBeenCalledTimes(1); expect(Array.isArray(agencies)).toBe(true); }); + + it('should return an empty array if the user is not found', async () => { + _usersFindOneBy.mockImplementationOnce(async () => null); + const agencies = await userServices.getAgencies('test'); + expect(Array.isArray(agencies)).toBe(true); + expect(agencies).toHaveLength(0); + }); + }); + + describe('hasAgencies', () => { + it('should return true if the user has the corresponding agencies', async () => { + _agenciesFind.mockImplementationOnce(async () => [produceAgency({ Id: 1 })]); + const result = await userServices.hasAgencies('test', [1]); + expect(result).toEqual(true); + }); + + it('should return false if the user does not have the corresponding agencies', async () => { + _agenciesFind.mockImplementationOnce(async () => [produceAgency({ Id: 2 })]); + const result = await userServices.hasAgencies('test', [1]); + expect(result).toEqual(false); + }); }); describe('normalizeKeycloakUser', () => { @@ -174,7 +223,15 @@ describe('UNIT - User services', () => { it('should throw an error if the user already exists', async () => { const user = produceUser(); _usersFindOne.mockResolvedValueOnce(user); - expect(async () => await userServices.addUser(user)).rejects.toThrow(); + expect(async () => await userServices.addUser(user)).rejects.toThrow( + 'Resource already exists.', + ); + }); + + it('should throw an error if the email is invalid', async () => { + const user = produceUser({ Email: 'blah' }); + _usersFindOne.mockResolvedValueOnce(null); + expect(async () => await userServices.addUser(user)).rejects.toThrow('Invalid email.'); }); }); describe('updateUser', () => { @@ -193,7 +250,14 @@ describe('UNIT - User services', () => { it('should throw an error if the user does not exist', () => { const user = produceUser(); _usersFindOne.mockResolvedValueOnce(undefined); - expect(async () => await userServices.updateUser(user)).rejects.toThrow(); + expect(async () => await userServices.updateUser(user)).rejects.toThrow( + 'Resource does not exist.', + ); + }); + + it('should throw an error if the email is invalid does not exist', () => { + const user = produceUser({ Email: 'blah' }); + expect(async () => await userServices.updateUser(user)).rejects.toThrow('Invalid email.'); }); }); describe('deleteUser', () => { From 930f156f8f6b75709d79fd2f56af63ed7085737a Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 2 Oct 2024 09:36:12 -0700 Subject: [PATCH 11/14] attempted test fix --- react-app/src/components/form/AutocompleteField.test.tsx | 5 +++++ react-app/src/components/layout/Footer.test.tsx | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/react-app/src/components/form/AutocompleteField.test.tsx b/react-app/src/components/form/AutocompleteField.test.tsx index a7f48cb9f..fa9c14131 100644 --- a/react-app/src/components/form/AutocompleteField.test.tsx +++ b/react-app/src/components/form/AutocompleteField.test.tsx @@ -15,6 +15,11 @@ jest.mock('react-hook-form', () => ({ }), })); +jest.mock('@mui/utils', () => ({ + ...jest.requireActual('@mui/utils'), + getReactNodeRef: () => null, +})); + describe('', () => { it('should render', () => { render( diff --git a/react-app/src/components/layout/Footer.test.tsx b/react-app/src/components/layout/Footer.test.tsx index c09778ab8..fc421c9ab 100644 --- a/react-app/src/components/layout/Footer.test.tsx +++ b/react-app/src/components/layout/Footer.test.tsx @@ -13,6 +13,11 @@ jest.mock('@mui/material', () => ({ }), })); +jest.mock('@mui/utils', () => ({ + ...jest.requireActual('@mui/utils'), + getReactNodeRef: () => null, +})); + describe('Footer.tsx', () => { it('should match the existing snapshot', () => { const tree = create(