From 7a25a2bad6f7ba0061a145ac056f945acd8cec46 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Thu, 9 May 2024 16:45:23 -0500 Subject: [PATCH 1/9] Remove unneeded renderHook The functions being tested are regular functions not hooks, so renderHook is not needed here. --- src/hooks/__tests__/useDataDog.test.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/hooks/__tests__/useDataDog.test.ts b/src/hooks/__tests__/useDataDog.test.ts index 8534694ed..791957e13 100644 --- a/src/hooks/__tests__/useDataDog.test.ts +++ b/src/hooks/__tests__/useDataDog.test.ts @@ -1,4 +1,3 @@ -import { renderHook } from '@testing-library/react-hooks'; import { clearDataDogUser, isDataDogConfigured, @@ -23,12 +22,11 @@ describe('useDataDog', () => { describe('DataDog not configured', () => { it('should return isDataDogConfigured as false', () => { - const { result } = renderHook(() => isDataDogConfigured(), {}); - expect(result.current).toEqual(false); + expect(isDataDogConfigured()).toEqual(false); }); it('should NOT run setDataDogUser', () => { - renderHook(() => setDataDogUser(setDataDogUserMock), {}); + setDataDogUser(setDataDogUserMock); expect(window.DD_RUM.getUser).not.toHaveBeenCalled(); expect(window.DD_RUM.clearUser).not.toHaveBeenCalled(); expect(window.DD_RUM.setUser).not.toHaveBeenCalled(); @@ -42,17 +40,16 @@ describe('useDataDog', () => { //#region Default Tests it('should return isDataDogConfigured as true', () => { - const { result } = renderHook(() => isDataDogConfigured(), {}); - expect(result.current).toEqual(true); + expect(isDataDogConfigured()).toEqual(true); }); it('should run clearDataDogUser', () => { - renderHook(() => clearDataDogUser(), {}); + clearDataDogUser(); expect(window.DD_RUM.clearUser).toHaveBeenCalled(); }); it('New User', () => { - renderHook(() => setDataDogUser(setDataDogUserMock), {}); + setDataDogUser(setDataDogUserMock); expect(window.DD_RUM.getUser).toHaveBeenCalledTimes(2); expect(window.DD_RUM.clearUser).toHaveBeenCalled(); expect(window.DD_RUM.setUser).toHaveBeenCalled(); From 08e97af6c1268ada6a0e998b298e0ff34ca6dc45 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Thu, 9 May 2024 16:46:05 -0500 Subject: [PATCH 2/9] Add DD_RUM types --- src/hooks/useDataDog.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hooks/useDataDog.ts b/src/hooks/useDataDog.ts index ae45f1107..92f826206 100644 --- a/src/hooks/useDataDog.ts +++ b/src/hooks/useDataDog.ts @@ -1,6 +1,10 @@ declare global { interface Window { - DD_RUM: any; + DD_RUM: { + getUser: () => Record | undefined; + setUser: (user: Record) => void; + clearUser: () => void; + }; } } From b3d245dd190ca67bf81b7061e37049971366585d Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Thu, 9 May 2024 16:47:53 -0500 Subject: [PATCH 3/9] Simplify DD_RUM.getUser existence check --- src/hooks/useDataDog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useDataDog.ts b/src/hooks/useDataDog.ts index 92f826206..1003056ae 100644 --- a/src/hooks/useDataDog.ts +++ b/src/hooks/useDataDog.ts @@ -14,7 +14,7 @@ export const isDataDogConfigured = (): boolean => { } return !!( process.env.DATADOG_CONFIGURED === 'true' && - (window?.DD_RUM ?? {}).hasOwnProperty('getUser') + window.DD_RUM?.hasOwnProperty('getUser') ); }; From 810a1070a1bb8ce95186622173d5ef02475dfa4b Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Fri, 10 May 2024 08:19:38 -0500 Subject: [PATCH 4/9] Rename hooks/useDataDog.ts to lib/dataDog.ts useDataDog did not export any hooks. --- pages/logout.page.tsx | 2 +- src/components/DataDog/DataDog.tsx | 2 +- .../NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx | 2 +- .../Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx | 2 +- .../Organization/Modals/OrganizationAddAccountModal.tsx | 2 +- src/lib/apollo/client.ts | 2 +- src/{hooks/__tests__/useDataDog.test.ts => lib/dataDog.test.ts} | 2 +- src/{hooks/useDataDog.ts => lib/dataDog.ts} | 0 8 files changed, 7 insertions(+), 7 deletions(-) rename src/{hooks/__tests__/useDataDog.test.ts => lib/dataDog.test.ts} (98%) rename src/{hooks/useDataDog.ts => lib/dataDog.ts} (100%) diff --git a/pages/logout.page.tsx b/pages/logout.page.tsx index 845e60875..851347580 100644 --- a/pages/logout.page.tsx +++ b/pages/logout.page.tsx @@ -7,8 +7,8 @@ import { Box, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { signOut } from 'next-auth/react'; import { useTranslation } from 'react-i18next'; -import { clearDataDogUser } from 'src/hooks/useDataDog'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; +import { clearDataDogUser } from 'src/lib/dataDog'; import { loadSession } from './api/utils/pagePropsHelpers'; const BoxWrapper = styled(Box)(({ theme }) => ({ diff --git a/src/components/DataDog/DataDog.tsx b/src/components/DataDog/DataDog.tsx index bb6b44250..0e4043fd0 100644 --- a/src/components/DataDog/DataDog.tsx +++ b/src/components/DataDog/DataDog.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import React, { useEffect } from 'react'; import { useSession } from 'next-auth/react'; -import { setDataDogUser } from 'src/hooks/useDataDog'; +import { setDataDogUser } from 'src/lib/dataDog'; const DataDog: React.FC = () => { const { query } = useRouter(); diff --git a/src/components/Layouts/Primary/NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx b/src/components/Layouts/Primary/NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx index 0b9877b3b..a856171c1 100644 --- a/src/components/Layouts/Primary/NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx +++ b/src/components/Layouts/Primary/NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx @@ -8,7 +8,7 @@ import { styled } from '@mui/material/styles'; import { signOut } from 'next-auth/react'; import { useTranslation } from 'react-i18next'; import { NextLinkComposed } from 'src/components/common/Links/NextLinkComposed'; -import { clearDataDogUser } from 'src/hooks/useDataDog'; +import { clearDataDogUser } from 'src/lib/dataDog'; import { useAccountListId } from '../../../../../../hooks/useAccountListId'; import theme from '../../../../../../theme'; import HandoffLink from '../../../../../HandoffLink'; diff --git a/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx b/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx index dfc5351a9..b561c7447 100644 --- a/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx @@ -22,8 +22,8 @@ import { styled } from '@mui/material/styles'; import { signOut } from 'next-auth/react'; import { useSnackbar } from 'notistack'; import { useTranslation } from 'react-i18next'; -import { clearDataDogUser } from 'src/hooks/useDataDog'; import { useRequiredSession } from 'src/hooks/useRequiredSession'; +import { clearDataDogUser } from 'src/lib/dataDog'; import { useAccountListId } from '../../../../../../hooks/useAccountListId'; import theme from '../../../../../../theme'; import HandoffLink from '../../../../../HandoffLink'; diff --git a/src/components/Settings/integrations/Organization/Modals/OrganizationAddAccountModal.tsx b/src/components/Settings/integrations/Organization/Modals/OrganizationAddAccountModal.tsx index 588358b0b..8dcc7b7ba 100644 --- a/src/components/Settings/integrations/Organization/Modals/OrganizationAddAccountModal.tsx +++ b/src/components/Settings/integrations/Organization/Modals/OrganizationAddAccountModal.tsx @@ -22,8 +22,8 @@ import { } from 'src/components/common/Modal/ActionButtons/ActionButtons'; import Modal from 'src/components/common/Modal/Modal'; import { Organization } from 'src/graphql/types.generated'; -import { clearDataDogUser } from 'src/hooks/useDataDog'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; +import { clearDataDogUser } from 'src/lib/dataDog'; import { articles, showArticle } from 'src/lib/helpScout'; import theme from 'src/theme'; import { useOauthUrl } from '../../useOauthUrl'; diff --git a/src/lib/apollo/client.ts b/src/lib/apollo/client.ts index 11f2d2b11..350d88466 100644 --- a/src/lib/apollo/client.ts +++ b/src/lib/apollo/client.ts @@ -2,7 +2,7 @@ import { ApolloClient, from } from '@apollo/client'; import { onError } from '@apollo/client/link/error'; import { LocalStorageWrapper, persistCache } from 'apollo3-cache-persist'; import { signOut } from 'next-auth/react'; -import { clearDataDogUser } from 'src/hooks/useDataDog'; +import { clearDataDogUser } from 'src/lib/dataDog'; import snackNotifications from '../../components/Snackbar/Snackbar'; import { dispatch } from '../analytics'; import { createCache } from './cache'; diff --git a/src/hooks/__tests__/useDataDog.test.ts b/src/lib/dataDog.test.ts similarity index 98% rename from src/hooks/__tests__/useDataDog.test.ts rename to src/lib/dataDog.test.ts index 791957e13..43178ab12 100644 --- a/src/hooks/__tests__/useDataDog.test.ts +++ b/src/lib/dataDog.test.ts @@ -2,7 +2,7 @@ import { clearDataDogUser, isDataDogConfigured, setDataDogUser, -} from '../useDataDog'; +} from './dataDog'; const setDataDogUserMock = { userId: '123456', diff --git a/src/hooks/useDataDog.ts b/src/lib/dataDog.ts similarity index 100% rename from src/hooks/useDataDog.ts rename to src/lib/dataDog.ts From 06691568f3e1ebe23056461bcacf811b947b46fb Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Fri, 10 May 2024 09:04:42 -0500 Subject: [PATCH 5/9] Improve test case names --- src/lib/dataDog.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/dataDog.test.ts b/src/lib/dataDog.test.ts index 43178ab12..f63e3c5fe 100644 --- a/src/lib/dataDog.test.ts +++ b/src/lib/dataDog.test.ts @@ -11,7 +11,7 @@ const setDataDogUserMock = { email: 'roger@cru.org', }; -describe('useDataDog', () => { +describe('dataDog', () => { beforeEach(() => { window.DD_RUM = { getUser: jest.fn(), @@ -20,12 +20,12 @@ describe('useDataDog', () => { }; }); - describe('DataDog not configured', () => { - it('should return isDataDogConfigured as false', () => { + describe('when DataDog is not configured', () => { + it('isDataDogConfigured should return false', () => { expect(isDataDogConfigured()).toEqual(false); }); - it('should NOT run setDataDogUser', () => { + it('setDataDogUser should not call DD_RUM methods', () => { setDataDogUser(setDataDogUserMock); expect(window.DD_RUM.getUser).not.toHaveBeenCalled(); expect(window.DD_RUM.clearUser).not.toHaveBeenCalled(); @@ -33,22 +33,22 @@ describe('useDataDog', () => { }); }); - describe('DataDog configured', () => { + describe('when DataDog is configured', () => { beforeEach(() => { process.env.DATADOG_CONFIGURED = 'true'; }); //#region Default Tests - it('should return isDataDogConfigured as true', () => { + it('isDataDogConfigured should return true', () => { expect(isDataDogConfigured()).toEqual(true); }); - it('should run clearDataDogUser', () => { + it('clearDataDogUser should clear the user', () => { clearDataDogUser(); expect(window.DD_RUM.clearUser).toHaveBeenCalled(); }); - it('New User', () => { + it('setDataDogUser should clear the previous user and set the new user', () => { setDataDogUser(setDataDogUserMock); expect(window.DD_RUM.getUser).toHaveBeenCalledTimes(2); expect(window.DD_RUM.clearUser).toHaveBeenCalled(); From f9e43192e4530ccc3defa0fb4154e28c0f6b4011 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Fri, 10 May 2024 09:07:18 -0500 Subject: [PATCH 6/9] Unconditionally set the DataDog user with the new attributes --- src/lib/dataDog.test.ts | 6 +----- src/lib/dataDog.ts | 14 ++------------ 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/lib/dataDog.test.ts b/src/lib/dataDog.test.ts index f63e3c5fe..2936094a6 100644 --- a/src/lib/dataDog.test.ts +++ b/src/lib/dataDog.test.ts @@ -14,7 +14,6 @@ const setDataDogUserMock = { describe('dataDog', () => { beforeEach(() => { window.DD_RUM = { - getUser: jest.fn(), setUser: jest.fn(), clearUser: jest.fn(), }; @@ -27,7 +26,6 @@ describe('dataDog', () => { it('setDataDogUser should not call DD_RUM methods', () => { setDataDogUser(setDataDogUserMock); - expect(window.DD_RUM.getUser).not.toHaveBeenCalled(); expect(window.DD_RUM.clearUser).not.toHaveBeenCalled(); expect(window.DD_RUM.setUser).not.toHaveBeenCalled(); }); @@ -48,10 +46,8 @@ describe('dataDog', () => { expect(window.DD_RUM.clearUser).toHaveBeenCalled(); }); - it('setDataDogUser should clear the previous user and set the new user', () => { + it('setDataDogUser should set the new user', () => { setDataDogUser(setDataDogUserMock); - expect(window.DD_RUM.getUser).toHaveBeenCalledTimes(2); - expect(window.DD_RUM.clearUser).toHaveBeenCalled(); expect(window.DD_RUM.setUser).toHaveBeenCalled(); }); }); diff --git a/src/lib/dataDog.ts b/src/lib/dataDog.ts index 1003056ae..1b95f0e7f 100644 --- a/src/lib/dataDog.ts +++ b/src/lib/dataDog.ts @@ -1,7 +1,6 @@ declare global { interface Window { DD_RUM: { - getUser: () => Record | undefined; setUser: (user: Record) => void; clearUser: () => void; }; @@ -14,7 +13,7 @@ export const isDataDogConfigured = (): boolean => { } return !!( process.env.DATADOG_CONFIGURED === 'true' && - window.DD_RUM?.hasOwnProperty('getUser') + window.DD_RUM?.hasOwnProperty('setUser') ); }; @@ -34,20 +33,11 @@ export const setDataDogUser = ({ if (!isDataDogConfigured()) { return; } - if ( - window.DD_RUM.getUser()?.accountListId && - window.DD_RUM.getUser()?.accountListId === accountListId - ) { - return; - } - if (window.DD_RUM.getUser()?.accountListId !== accountListId) { - clearDataDogUser(); - } window.DD_RUM.setUser({ id: accountListId, name, email, - userId: userId, + userId, }); }; From fd470fc1439de8f5f280a3aaf6b7cafc142190b7 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Fri, 10 May 2024 11:12:42 -0500 Subject: [PATCH 7/9] Remember the user's previous account list ids --- src/lib/dataDog.test.ts | 63 +++++++++++++++++++++++++++++++++++++++++ src/lib/dataDog.ts | 20 +++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/lib/dataDog.test.ts b/src/lib/dataDog.test.ts index 2936094a6..42073826a 100644 --- a/src/lib/dataDog.test.ts +++ b/src/lib/dataDog.test.ts @@ -1,4 +1,5 @@ import { + accountListIdsStorageKey, clearDataDogUser, isDataDogConfigured, setDataDogUser, @@ -51,4 +52,66 @@ describe('dataDog', () => { expect(window.DD_RUM.setUser).toHaveBeenCalled(); }); }); + + describe('setDataDogUser', () => { + beforeEach(() => { + process.env.DATADOG_CONFIGURED = 'true'; + }); + + it('adds new account list ids to the list', () => { + window.localStorage.setItem(accountListIdsStorageKey, 'previous'); + + setDataDogUser(setDataDogUserMock); + expect(window.DD_RUM.setUser).toHaveBeenCalledWith( + expect.objectContaining({ + accountListIds: ['previous', setDataDogUserMock.accountListId], + }), + ); + expect(window.localStorage.getItem(accountListIdsStorageKey)).toBe( + `previous,${setDataDogUserMock.accountListId}`, + ); + }); + + it('does not add blank account list ids to the list', () => { + window.localStorage.removeItem(accountListIdsStorageKey); + + setDataDogUser({ ...setDataDogUserMock, accountListId: '' }); + expect(window.DD_RUM.setUser).toHaveBeenCalledWith( + expect.objectContaining({ accountListIds: [] }), + ); + expect(window.localStorage.getItem(accountListIdsStorageKey)).toBeNull(); + }); + + it('does not add duplicate account list ids to the list', () => { + window.localStorage.setItem( + accountListIdsStorageKey, + setDataDogUserMock.accountListId, + ); + + setDataDogUser(setDataDogUserMock); + expect(window.DD_RUM.setUser).toHaveBeenCalledWith( + expect.objectContaining({ + accountListIds: [setDataDogUserMock.accountListId], + }), + ); + expect(window.localStorage.getItem(accountListIdsStorageKey)).toBe( + setDataDogUserMock.accountListId, + ); + }); + + it('resets the account list ids list after calling clearDataDogUser', () => { + window.localStorage.setItem(accountListIdsStorageKey, 'previous'); + clearDataDogUser(); + + setDataDogUser(setDataDogUserMock); + expect(window.DD_RUM.setUser).toHaveBeenCalledWith( + expect.objectContaining({ + accountListIds: [setDataDogUserMock.accountListId], + }), + ); + expect(window.localStorage.getItem(accountListIdsStorageKey)).toBe( + setDataDogUserMock.accountListId, + ); + }); + }); }); diff --git a/src/lib/dataDog.ts b/src/lib/dataDog.ts index 1b95f0e7f..522639be9 100644 --- a/src/lib/dataDog.ts +++ b/src/lib/dataDog.ts @@ -1,7 +1,7 @@ declare global { interface Window { DD_RUM: { - setUser: (user: Record) => void; + setUser: (user: Record) => void; clearUser: () => void; }; } @@ -24,6 +24,8 @@ export interface SetDataDogUserProps { accountListId: string; } +export const accountListIdsStorageKey = 'accountListIds'; + export const setDataDogUser = ({ userId, name, @@ -33,11 +35,22 @@ export const setDataDogUser = ({ if (!isDataDogConfigured()) { return; } + const rawAccountListIds = window.localStorage.getItem( + accountListIdsStorageKey, + ); + const accountListIds = rawAccountListIds ? rawAccountListIds.split(',') : []; + if (accountListId && !accountListIds.includes(accountListId)) { + accountListIds.push(accountListId); + window.localStorage.setItem( + accountListIdsStorageKey, + accountListIds.join(','), + ); + } window.DD_RUM.setUser({ - id: accountListId, + id: userId, name, email, - userId, + accountListIds, }); }; @@ -46,4 +59,5 @@ export const clearDataDogUser = (): void => { return; } window.DD_RUM.clearUser(); + window.localStorage.removeItem(accountListIdsStorageKey); }; From 6e9f763d27e7f00e617568d8dbb682d245cebd20 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Fri, 10 May 2024 12:34:04 -0500 Subject: [PATCH 8/9] Stop passing a blank accountListId to setDataDogUser --- src/components/DataDog/DataDog.tsx | 2 +- src/lib/dataDog.test.ts | 4 ++-- src/lib/dataDog.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/DataDog/DataDog.tsx b/src/components/DataDog/DataDog.tsx index 0e4043fd0..c92c9c505 100644 --- a/src/components/DataDog/DataDog.tsx +++ b/src/components/DataDog/DataDog.tsx @@ -11,7 +11,7 @@ const DataDog: React.FC = () => { ? Array.isArray(query.accountListId) ? query.accountListId[0] : query.accountListId - : ''; + : null; const user = session?.user; useEffect(() => { diff --git a/src/lib/dataDog.test.ts b/src/lib/dataDog.test.ts index 42073826a..ecef105d9 100644 --- a/src/lib/dataDog.test.ts +++ b/src/lib/dataDog.test.ts @@ -72,10 +72,10 @@ describe('dataDog', () => { ); }); - it('does not add blank account list ids to the list', () => { + it('does not add null account list ids to the list', () => { window.localStorage.removeItem(accountListIdsStorageKey); - setDataDogUser({ ...setDataDogUserMock, accountListId: '' }); + setDataDogUser({ ...setDataDogUserMock, accountListId: null }); expect(window.DD_RUM.setUser).toHaveBeenCalledWith( expect.objectContaining({ accountListIds: [] }), ); diff --git a/src/lib/dataDog.ts b/src/lib/dataDog.ts index 522639be9..79c520e5f 100644 --- a/src/lib/dataDog.ts +++ b/src/lib/dataDog.ts @@ -21,7 +21,7 @@ export interface SetDataDogUserProps { userId: string; name: string; email: string; - accountListId: string; + accountListId: string | null; } export const accountListIdsStorageKey = 'accountListIds'; From 0e63121bd96d469bbb7b8334ff30f021f3672eec Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Mon, 13 May 2024 12:16:55 -0500 Subject: [PATCH 9/9] Add DataDog tests --- src/components/DataDog/DataDog.test.tsx | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/components/DataDog/DataDog.test.tsx diff --git a/src/components/DataDog/DataDog.test.tsx b/src/components/DataDog/DataDog.test.tsx new file mode 100644 index 000000000..fb46415d4 --- /dev/null +++ b/src/components/DataDog/DataDog.test.tsx @@ -0,0 +1,59 @@ +import { render } from '@testing-library/react'; +import { useSession } from 'next-auth/react'; +import TestRouter from '__tests__/util/TestRouter'; +import { setDataDogUser } from 'src/lib/dataDog'; +import DataDog from './DataDog'; + +jest.mock('src/lib/dataDog'); + +const accountListId = 'account-list-1'; +const router = { + query: { accountListId }, + isReady: true, +}; + +const TestComponent: React.FC = () => ( + + + +); + +describe('DataDog', () => { + it('calls setDataDogUser with the user', () => { + render(); + + expect(setDataDogUser).toHaveBeenCalledWith({ + accountListId: 'account-list-1', + email: 'first.last@cru.org', + name: 'First Last', + userId: 'user-1', + }); + }); + + it('does not call setDataDogUser if there is no session', () => { + (useSession as jest.MockedFn).mockReturnValueOnce({ + data: null, + status: 'unauthenticated', + update: () => Promise.resolve(null), + }); + + render(); + + expect(setDataDogUser).not.toHaveBeenCalled(); + }); + + it('handles missing accountListId', () => { + render( + + + , + ); + + expect(setDataDogUser).toHaveBeenCalledWith({ + accountListId: null, + email: 'first.last@cru.org', + name: 'First Last', + userId: 'user-1', + }); + }); +});