diff --git a/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.test.tsx b/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.test.tsx index da926e939c..b819f29178 100644 --- a/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.test.tsx @@ -18,7 +18,7 @@ const mutationSpy = jest.fn(); interface ComponentProps { userType?: UserTypeEnum; - staffAccountId?: string; + staffAccountId?: string | null; } const Components: React.FC = ({ @@ -76,29 +76,8 @@ describe('Partner Reminders Report Page', () => { }); it('renders even though there is no staff account', async () => { - const mockNoStaffAccount = { - GetUser: { - user: { - staffAccountId: null, - }, - }, - }; - const { findByRole } = render( - - - - - mocks={mockNoStaffAccount} - onCall={mutationSpy} - > - - - - - , + , ); expect( @@ -106,6 +85,19 @@ describe('Partner Reminders Report Page', () => { ).toBeInTheDocument(); }); + it('renders limited access page when user group is not US or hybrid staff', async () => { + const { findByText, getByText } = render( + , + ); + + expect( + await findByText(/access to this feature is limited/i), + ).toBeInTheDocument(); + expect( + getByText(/our records show that you are not part of the user group/i), + ).toBeInTheDocument(); + }); + it('uses blockImpersonatingNonDevelopers for server-side props', () => { expect(getServerSideProps).toBe(blockImpersonatingNonDevelopers); }); diff --git a/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.tsx b/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.tsx index 0c94907b70..ec9126c2c1 100644 --- a/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.tsx +++ b/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.tsx @@ -10,6 +10,7 @@ import { MultiPageMenu, NavTypeEnum, } from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu'; +import { UserTypeAccess } from 'src/components/Shared/UserTypeAccess/UserTypeAccess'; import { getAppName } from 'src/lib/getAppName'; const PartnerRemindersReportPageWrapper = styled(Box)(({ theme }) => ({ @@ -31,28 +32,30 @@ const PartnerRemindersReportPage: React.FC = () => { {`${appName} | ${t('HR Tools | Ministry Partner Reminders')}`} - - - } - leftOpen={isNavListOpen} - leftWidth="290px" - mainContent={ - - } - /> - + + + + } + leftOpen={isNavListOpen} + leftWidth="290px" + mainContent={ + + } + /> + + ); }; diff --git a/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.test.tsx b/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.test.tsx index 5240a4bf8d..7c02a4454d 100644 --- a/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.test.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.test.tsx @@ -123,7 +123,7 @@ describe('NavMenu', () => { }); it('renders HR Tools submenu items', async () => { - const { findByRole, getByRole, getByTestId, queryByRole } = render( + const { findByRole, getByRole, getByTestId } = render( { getByRole('menuitem', { name: 'Additional Salary Request' }), ).toBeInTheDocument(); expect( - queryByRole('menuitem', { name: 'Ministry Partner Reminders' }), - ).not.toBeInTheDocument(); + getByRole('menuitem', { name: 'Ministry Partner Reminders' }), + ).toBeInTheDocument(); }); it('renders MPDX Tools submenu items', async () => { @@ -196,11 +196,17 @@ describe('NavMenu', () => { expect(getByTestId('appeals-false')).toBeInTheDocument(); }); - it('does not render new reports when user type not verified', async () => { - const { queryByRole } = render( + it('shows only Partner Reminders in HR Tools when user type not verified', async () => { + const { findByRole, getByRole, getByTestId, queryByRole } = render( { />, ); - await waitFor(() => { - expect( - queryByRole('menuitem', { name: 'HR Tools' }), - ).not.toBeInTheDocument(); - }); + await findByRole('menuitem', { name: 'HR Tools' }); + userEvent.click(getByTestId('HrToolsMenuToggle')); + + // Partner Reminders is live regardless of verification, so the tab still shows + expect( + getByRole('menuitem', { name: 'Ministry Partner Reminders' }), + ).toBeInTheDocument(); + + expect( + queryByRole('menuitem', { name: 'Salary Calculation Form' }), + ).not.toBeInTheDocument(); + expect( + queryByRole('menuitem', { name: 'MPD Goal Calculator' }), + ).not.toBeInTheDocument(); }); it('does not show coaching link if there are no coaching accounts', async () => { diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx index 64f5600dab..e740cf7afb 100644 --- a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx @@ -513,7 +513,10 @@ describe('MultiPageMenu', () => { const { findByText, getByText, queryByText } = render( - + mocks={{ GetUser: { user: { @@ -522,6 +525,11 @@ describe('MultiPageMenu', () => { staffAccountId: '12345', }, }, + UserOption: { + userOption: { + value: 'true', + }, + }, }} > { expect(getByText('MPD Goal Calculator')).toBeInTheDocument(); expect(getByText('MHA Calculation Tool')).toBeInTheDocument(); expect(getByText('Additional Salary Request')).toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); expect( queryByText('Paid with Designation Support Goal Calculator'), ).not.toBeInTheDocument(); expect(queryByText('New Staff Goal Calculator')).not.toBeInTheDocument(); - - expect(queryByText('Ministry Partner Reminders')).not.toBeInTheDocument(); }); it('shows hr tools for new staff', async () => { const { findByText, getByText, queryByText } = render( - + mocks={{ GetUser: { user: { @@ -563,6 +573,11 @@ describe('MultiPageMenu', () => { staffAccountId: '12345', }, }, + UserOption: { + userOption: { + value: 'true', + }, + }, }} > { expect(getByText('New Staff Goal Calculator')).toBeInTheDocument(); expect(queryByText('MPD Goal Calculator')).not.toBeInTheDocument(); expect(queryByText('MHA Calculation Tool')).not.toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); expect( queryByText('Paid with Designation Support Goal Calculator'), ).not.toBeInTheDocument(); - - expect(queryByText('Ministry Partner Reminders')).not.toBeInTheDocument(); }); it('shows hr tools for national expat staff', async () => { const { findByText, getByText, queryByText } = render( - + mocks={{ GetUser: { user: { @@ -604,6 +621,11 @@ describe('MultiPageMenu', () => { staffAccountId: '12345', }, }, + UserOption: { + userOption: { + value: 'true', + }, + }, }} > { expect(queryByText('MPD Goal Calculator')).not.toBeInTheDocument(); expect(getByText('MHA Calculation Tool')).toBeInTheDocument(); expect(getByText('Additional Salary Request')).toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); expect( queryByText('Paid with Designation Support Goal Calculator'), ).not.toBeInTheDocument(); expect(queryByText('New Staff Goal Calculator')).not.toBeInTheDocument(); - - expect(queryByText('Ministry Partner Reminders')).not.toBeInTheDocument(); }); it('shows hr tools for paid with designation', async () => { - const { findByText, queryByText } = render( + const { findByText, queryByText, getByText } = render( - + mocks={{ GetUser: { user: { @@ -645,6 +669,11 @@ describe('MultiPageMenu', () => { staffAccountId: null, }, }, + UserOption: { + userOption: { + value: 'true', + }, + }, }} > { expect( await findByText('Paid with Designation Support Goal Calculator'), ).toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); expect(queryByText('Salary Calculation Form')).not.toBeInTheDocument(); expect(queryByText('Additional Salary Request')).not.toBeInTheDocument(); expect(queryByText('Savings Fund Transfer')).not.toBeInTheDocument(); expect(queryByText('MPD Goal Calculator')).not.toBeInTheDocument(); expect(queryByText('MHA Calculation Tool')).not.toBeInTheDocument(); expect(queryByText('New Staff Goal Calculator')).not.toBeInTheDocument(); - expect(queryByText('Ministry Partner Reminders')).not.toBeInTheDocument(); }); it('shows hr tools for part time field staff', async () => { - const { findByText, queryByText } = render( + const { findByText, queryByText, getByText } = render( - + mocks={{ GetUser: { user: { @@ -685,6 +717,11 @@ describe('MultiPageMenu', () => { staffAccountId: '12345', }, }, + UserOption: { + userOption: { + value: 'true', + }, + }, }} > { ); expect(await findByText('Savings Fund Transfer')).toBeInTheDocument(); - expect(queryByText('Ministry Partner Reminders')).not.toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); expect( queryByText('Paid with Designation Support Goal Calculator'), ).not.toBeInTheDocument(); @@ -713,10 +750,13 @@ describe('MultiPageMenu', () => { }); it('shows hr tools for interns', async () => { - const { findByText, queryByText } = render( + const { findByText, queryByText, getByText } = render( - + mocks={{ GetUser: { user: { @@ -725,6 +765,11 @@ describe('MultiPageMenu', () => { staffAccountId: '12345', }, }, + UserOption: { + userOption: { + value: 'true', + }, + }, }} > { ); expect(await findByText('Savings Fund Transfer')).toBeInTheDocument(); - expect(queryByText('Ministry Partner Reminders')).not.toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); expect( queryByText('Paid with Designation Support Goal Calculator'), ).not.toBeInTheDocument(); diff --git a/src/hooks/useHrToolsNavItems.test.tsx b/src/hooks/useHrToolsNavItems.test.tsx index c11186e3ae..0845630536 100644 --- a/src/hooks/useHrToolsNavItems.test.tsx +++ b/src/hooks/useHrToolsNavItems.test.tsx @@ -4,6 +4,7 @@ import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { mockSession } from '__tests__/util/mockSession'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; +import { UserOptionQuery } from './UserPreference.generated'; import { useHrToolsNavItems } from './useHrToolsNavItems'; // A user in a group that is ineligible for every HR Tool and with no staff account @@ -31,8 +32,11 @@ const Wrapper = ({ children }: { children: ReactElement }) => ( ); const MpdGoalEligibleWrapper = ({ children }: { children: ReactElement }) => ( - - mocks={{ GetUser: { user: mpdGoalEligibleUser } }} + + mocks={{ + GetUser: { user: mpdGoalEligibleUser }, + UserOption: { userOption: { key: 'user_type_verified', value: 'true' } }, + }} > {children} @@ -42,16 +46,33 @@ describe('useHrToolsNavItems', () => { afterEach(() => { process.env.DEVELOPMENT_ENV = 'false'; process.env.DISABLE_MPD_GOAL_ADMIN = 'false'; + process.env.DISABLE_NEW_REPORTS = 'false'; }); - it('hides all items for an ineligible user when not in a development env', async () => { + it('hides all items, except partner reminders, for an ineligible user when not in a development env', async () => { const { result, waitForNextUpdate } = renderHook( () => useHrToolsNavItems(), { wrapper: Wrapper }, ); await waitForNextUpdate(); - expect(result.current.items).toHaveLength(0); + expect(result.current.items).toHaveLength(1); + expect(result.current.items[0].id).toBe('partnerReminders'); + }); + + it('hides every eligible item except partner reminders when DISABLE_NEW_REPORTS is on', async () => { + process.env.DISABLE_NEW_REPORTS = 'true'; + mockSession({ developer: false }); + + const { result, waitForNextUpdate } = renderHook( + () => useHrToolsNavItems(), + { wrapper: MpdGoalEligibleWrapper }, + ); + await waitForNextUpdate(); + + expect(result.current.items.map((item) => item.id)).toEqual([ + 'partnerReminders', + ]); }); it('shows all items for an ineligible developer in a development env', async () => { @@ -111,7 +132,7 @@ describe('useHrToolsNavItems', () => { ); }); - it('hides all items for an ineligible non-developer in a development env', async () => { + it('hides all items, except partner reminders, for an ineligible non-developer in a development env', async () => { process.env.DEVELOPMENT_ENV = 'true'; mockSession({ developer: false }); @@ -121,10 +142,11 @@ describe('useHrToolsNavItems', () => { ); await waitForNextUpdate(); - expect(result.current.items).toHaveLength(0); + expect(result.current.items).toHaveLength(1); + expect(result.current.items[0].id).toBe('partnerReminders'); }); - it('hides all items for an ineligible developer outside a development env', async () => { + it('hides all items, except partner reminders, for an ineligible developer outside a development env', async () => { process.env.DEVELOPMENT_ENV = 'false'; mockSession({ developer: true }); @@ -134,6 +156,7 @@ describe('useHrToolsNavItems', () => { ); await waitForNextUpdate(); - expect(result.current.items).toHaveLength(0); + expect(result.current.items).toHaveLength(1); + expect(result.current.items[0].id).toBe('partnerReminders'); }); }); diff --git a/src/hooks/useHrToolsNavItems.ts b/src/hooks/useHrToolsNavItems.ts index a786577dd4..b0f2f914fb 100644 --- a/src/hooks/useHrToolsNavItems.ts +++ b/src/hooks/useHrToolsNavItems.ts @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'; import { useDeveloperBypass } from './useDeveloperBypass'; import { useIneligibleByGroup } from './useIneligibleByGroup'; import { NavItems } from './useReportNavItems'; +import { useReportsDisabled } from './useReportsDisabled'; export function useHrToolsNavItems(): { items: NavItems[]; @@ -20,6 +21,8 @@ export function useHrToolsNavItems(): { userLoading, } = useIneligibleByGroup(); const developerBypass = useDeveloperBypass(); + // Partner Reminders is live in production; every other HR Tool is still disabled + const { reportsDisabled } = useReportsDisabled(); const items = useMemo(() => { if (userLoading) { @@ -30,17 +33,18 @@ export function useHrToolsNavItems(): { { id: 'salaryCalculator', title: t('Salary Calculation Form'), - hideItem: inSalaryCalcIneligibleGroup, + hideItem: reportsDisabled || inSalaryCalcIneligibleGroup, }, { id: 'staffSavingFund', title: t('Savings Fund Transfer'), - hideItem: hasNoStaffAccount, + hideItem: reportsDisabled || hasNoStaffAccount, }, { id: 'nsGoalCalculator', title: t('New Staff Goal Calculator'), hideItem: + reportsDisabled || process.env.DISABLE_NS_GOAL_CALCULATOR === 'true' || inNsGoalCalcIneligibleGroup, }, @@ -48,45 +52,48 @@ export function useHrToolsNavItems(): { id: 'nsoMpdQuestionnaire', title: t('NSO MPD Questionnaire'), hideItem: + reportsDisabled || process.env.DISABLE_NS_GOAL_CALCULATOR === 'true' || inNsGoalCalcIneligibleGroup, }, { id: 'goalCalculator', title: t('MPD Goal Calculator'), - hideItem: inMpdGoalCalcIneligibleGroup, + hideItem: reportsDisabled || inMpdGoalCalcIneligibleGroup, }, { id: 'mpdGoalAdmin', title: t('MPD Goal Calculator Admin Table'), hideItem: + reportsDisabled || process.env.DISABLE_MPD_GOAL_ADMIN === 'true' || inMpdGoalCalcIneligibleGroup, }, { id: 'mhaCalculator', title: t('MHA Calculation Tool'), - hideItem: inMhaIneligibleGroup, + hideItem: reportsDisabled || inMhaIneligibleGroup, }, { id: 'additionalSalaryRequest', title: t('Additional Salary Request'), - hideItem: inAsrIneligibleGroup, + hideItem: reportsDisabled || inAsrIneligibleGroup, }, { id: 'pdsGoalCalculator', title: t('Paid with Designation Support Goal Calculator'), - hideItem: inPdsGoalCalcIneligibleGroup, + hideItem: reportsDisabled || inPdsGoalCalcIneligibleGroup, }, { id: 'partnerReminders', title: t('Ministry Partner Reminders'), - hideItem: !developerBypass, + // TODO (MPDX-9822): Once HCM goes live, add has no staff account gate back + hideItem: false, }, { id: 'mpdSupervisorReport', title: t('MPD Supervisor Report'), - hideItem: hasNoStaffAccount, + hideItem: reportsDisabled || hasNoStaffAccount, }, ].filter((item) => developerBypass || !item.hideItem); }, [ @@ -100,6 +107,7 @@ export function useHrToolsNavItems(): { userLoading, hasNoStaffAccount, developerBypass, + reportsDisabled, ]); return { items, loading: userLoading }; diff --git a/src/hooks/useNavPages.test.tsx b/src/hooks/useNavPages.test.tsx index 40c4a4f566..7e2b60880b 100644 --- a/src/hooks/useNavPages.test.tsx +++ b/src/hooks/useNavPages.test.tsx @@ -11,14 +11,14 @@ import { useNavPages } from './useNavPages'; const accountListId = 'account-list-1'; // Wrapper for asserting HR Tools visibility by user type (verified, reports enabled) -const makeWrapper = (userType: UserTypeEnum) => { +const makeWrapper = (userType: UserTypeEnum, userTypeVerified = 'true') => { const wrapper = ({ children }: { children: ReactElement }) => ( mocks={{ GetUser: { user: { userType } }, UserOption: { - userOption: { key: 'user_type_verified', value: 'true' }, + userOption: { key: 'user_type_verified', value: userTypeVerified }, }, }} > @@ -137,17 +137,39 @@ describe('useNavPages', () => { ); }); - it('keeps the HR Tools tab hidden for a developer when reports are disabled', async () => { + it('shows the HR Tools tab for a developer when reports are disabled', async () => { process.env.DEVELOPMENT_ENV = 'true'; mockSession({ developer: true }); - const { result, waitForNextUpdate } = renderHook(() => useNavPages(false), { + const { result, waitFor } = renderHook(() => useNavPages(false), { wrapper: ReportsDisabledWrapper, }); - await waitForNextUpdate(); - expect(result.current.navPages.map((page) => page.id)).not.toContain( - 'hr-tools-page', + await waitFor(() => + expect(result.current.navPages.map((page) => page.id)).toContain( + 'hr-tools-page', + ), + ); + }); + + it('shows only Partner Reminders in the HR Tools tab when reports are disabled', async () => { + mockSession({ developer: false }); + + const { result, waitFor } = renderHook(() => useNavPages(false), { + wrapper: makeWrapper(UserTypeEnum.UsStaff, 'false'), + }); + + await waitFor(() => + expect(result.current.navPages.map((page) => page.id)).toContain( + 'hr-tools-page', + ), + ); + + const hrToolsPage = result.current.navPages.find( + (page) => page.id === 'hr-tools-page', ); + expect(hrToolsPage?.items?.map((item) => item.id)).toEqual([ + 'partnerReminders', + ]); }); }); diff --git a/src/hooks/useNavPages.tsx b/src/hooks/useNavPages.tsx index ae3eeae0be..041ec97c58 100644 --- a/src/hooks/useNavPages.tsx +++ b/src/hooks/useNavPages.tsx @@ -8,7 +8,6 @@ import { useAccountListId } from 'src/hooks/useAccountListId'; import { useDeveloperBypass } from './useDeveloperBypass'; import { useHrToolsNavItems } from './useHrToolsNavItems'; import { useReportNavItems } from './useReportNavItems'; -import { useReportsDisabled } from './useReportsDisabled'; import { useSettingsNavItems } from './useSettingsNavItems'; import { useToolsNavItems } from './useToolsNavItems'; @@ -53,7 +52,6 @@ export function useNavPages(coachingAccountCount: boolean, isSearch = false) { const accountListId = useAccountListId(); const { t } = useTranslation(); const { data } = useGetUserQuery(); - const { reportsDisabled } = useReportsDisabled(); const userType = data?.user.userType; const developerBypass = useDeveloperBypass(); @@ -110,27 +108,23 @@ export function useNavPages(coachingAccountCount: boolean, isSearch = false) { })), showInNav: true, }, - ...(reportsDisabled - ? [] - : [ - { - id: 'hr-tools-page', - title: t('HR Tools'), - pathname: '/accountLists/[accountListId]/hrTools', - items: hrToolsItems.map((item) => ({ - ...item, - href: `/accountLists/${accountListId}/hrTools/${item.id}`, - searchIcon: , - searchName: t(`HR Tools - {{ title }}`, { title: item.title }), - showInSearchDialog: true, - })), - showInNav: true, - hideTab: - (!!data && !canSeeHrTools) || - hrToolsLoading || - hrToolsItems.length === 0, - }, - ]), + { + id: 'hr-tools-page', + title: t('HR Tools'), + pathname: '/accountLists/[accountListId]/hrTools', + items: hrToolsItems.map((item) => ({ + ...item, + href: `/accountLists/${accountListId}/hrTools/${item.id}`, + searchIcon: , + searchName: t(`HR Tools - {{ title }}`, { title: item.title }), + showInSearchDialog: true, + })), + showInNav: true, + hideTab: + (!!data && !canSeeHrTools) || + hrToolsLoading || + hrToolsItems.length === 0, + }, { id: 'mpdx-tools-page', title: t('MPDX Tools'), @@ -198,7 +192,6 @@ export function useNavPages(coachingAccountCount: boolean, isSearch = false) { hrToolsItems, hrToolsLoading, canSeeHrTools, - reportsDisabled, data, ]);