From 5ec62c49189a98b7700fdfbe90d44db42942a12a Mon Sep 17 00:00:00 2001 From: Katelyn Grimes Date: Wed, 27 May 2026 09:08:57 -0400 Subject: [PATCH 1/2] Add new usStaffGroup field --- .../index.page.test.tsx | 60 +-- .../[goalCalculationId].page.test.tsx | 28 +- .../goalCalculator/index.page.test.tsx | 28 +- .../hrTools/mhaCalculator/index.page.test.tsx | 48 +-- .../partnerReminders/index.page.test.tsx | 25 +- .../[pdsGoalId].page.test.tsx | 68 ++-- .../mpgaIncomeExpenses/index.page.test.tsx | 28 +- .../StaffSavingFundLayout.test.tsx | 8 +- .../MultiPageMenu/MultiPageMenu.test.tsx | 342 ++++++++++------ .../UserTypeAccess/UserTypeAccess.test.tsx | 79 +--- .../Shared/UserTypeAccess/UserTypeAccess.tsx | 34 +- src/components/User/GetUser.graphql | 1 + src/hooks/useHrToolsNavItems.ts | 21 +- src/hooks/useIneligibleByGroup.test.tsx | 166 ++++++++ src/hooks/useIneligibleByGroup.ts | 49 +++ src/hooks/useNavPages.tsx | 3 +- src/hooks/useUsStaffGroups.test.tsx | 384 ------------------ src/hooks/useUsStaffGroups.ts | 99 ----- 18 files changed, 576 insertions(+), 895 deletions(-) create mode 100644 src/hooks/useIneligibleByGroup.test.tsx create mode 100644 src/hooks/useIneligibleByGroup.ts delete mode 100644 src/hooks/useUsStaffGroups.test.tsx delete mode 100644 src/hooks/useUsStaffGroups.ts diff --git a/pages/accountLists/[accountListId]/hrTools/additionalSalaryRequest/index.page.test.tsx b/pages/accountLists/[accountListId]/hrTools/additionalSalaryRequest/index.page.test.tsx index 736bd18461..f25089291c 100644 --- a/pages/accountLists/[accountListId]/hrTools/additionalSalaryRequest/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/additionalSalaryRequest/index.page.test.tsx @@ -9,60 +9,42 @@ import { beforeTestResizeObserver, } from '__tests__/util/windowResizeObserver'; import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers'; -import { StaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccount.generated'; +import { HcmQuery } from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; -import { - PeopleGroupSupportTypeEnum, - UserPersonTypeEnum, - UserTypeEnum, -} from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import theme from 'src/theme'; import AdditionalSalaryRequestPage, { getServerSideProps } from './index.page'; -const mocks = { - StaffAccount: { - staffAccount: { - id: '12345', - name: 'Test Account', - }, - }, - AdditionalSalaryRequest: { - latestAdditionalSalaryRequest: null, - }, - Hcm: { - hcm: [ - { - salaryRequestEligible: true, - asrEit: { - asrEligibility: true, - }, - staffInfo: { - preferredName: 'Test User', - peopleGroupSupportType: PeopleGroupSupportTypeEnum.SupportedRmo, - userPersonType: UserPersonTypeEnum.EmployeeStaff, - }, - }, - ], - }, -}; interface TestComponentProps { userType?: UserTypeEnum; + usStaffGroup?: UsStaffGroupEnum.SeniorStaff; + staffAccountId?: string; } const TestComponent: React.FC = ({ userType = UserTypeEnum.UsStaff, + usStaffGroup = UsStaffGroupEnum.SeniorStaff, + staffAccountId = 'account-list-1', }) => ( mocks={{ - ...mocks, GetUser: { - user: { userType }, + user: { userType, usStaffGroup, staffAccountId }, + }, + Hcm: { + hcm: [ + { + asrEit: { + asrEligibility: true, + }, + }, + ], }, }} > @@ -109,8 +91,10 @@ describe('AdditionalSalaryRequest page', () => { it('renders no staff account page when no staff account', async () => { const mockNoStaffAccount = { - StaffAccount: { - staffAccount: null, + GetUser: { + user: { + staffAccountId: null, + }, }, }; @@ -119,7 +103,7 @@ describe('AdditionalSalaryRequest page', () => { mocks={mockNoStaffAccount} > diff --git a/pages/accountLists/[accountListId]/hrTools/goalCalculator/[goalCalculationId].page.test.tsx b/pages/accountLists/[accountListId]/hrTools/goalCalculator/[goalCalculationId].page.test.tsx index 96c911e715..fdd9c58a55 100644 --- a/pages/accountLists/[accountListId]/hrTools/goalCalculator/[goalCalculationId].page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/goalCalculator/[goalCalculationId].page.test.tsx @@ -3,12 +3,8 @@ import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { render } from '__tests__/util/testingLibraryReactMock'; import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers'; -import { HcmQuery } from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; -import { - PeopleGroupSupportTypeEnum, - UserTypeEnum, -} from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import { GoalCalculatorPage, getServerSideProps, @@ -16,30 +12,19 @@ import { interface ComponentsProps { userType?: UserTypeEnum; - peopleGroupSupportType?: PeopleGroupSupportTypeEnum; - salaryRequestEligible?: boolean; + usStaffGroup?: UsStaffGroupEnum; } const Components: React.FC = ({ userType = UserTypeEnum.NonCru, - peopleGroupSupportType = PeopleGroupSupportTypeEnum.SupportedRmo, - salaryRequestEligible = true, + usStaffGroup = UsStaffGroupEnum.SeniorStaff, }) => ( mocks={{ - GetUser: { user: { userType } }, - Hcm: { - hcm: [ - { - salaryRequestEligible, - staffInfo: { peopleGroupSupportType }, - }, - ], - }, + GetUser: { user: { userType, usStaffGroup } }, }} > @@ -60,12 +45,11 @@ describe('[goalCalculationId] page', () => { ).toBeInTheDocument(); }); - it('should show limited access for a US Staff user whose peopleGroupSupportType is Designation (PDS)', async () => { + it('should show limited access for a US Staff user whose usStaffGroup is PaidWithDesignation (PDS)', async () => { const { findByText } = render( , ); diff --git a/pages/accountLists/[accountListId]/hrTools/goalCalculator/index.page.test.tsx b/pages/accountLists/[accountListId]/hrTools/goalCalculator/index.page.test.tsx index 5864280f9f..477b184610 100644 --- a/pages/accountLists/[accountListId]/hrTools/goalCalculator/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/goalCalculator/index.page.test.tsx @@ -2,40 +2,25 @@ import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { render } from '__tests__/util/testingLibraryReactMock'; import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers'; -import { HcmQuery } from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; -import { - PeopleGroupSupportTypeEnum, - UserTypeEnum, -} from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import { GoalCalculatorPage, getServerSideProps } from './index.page'; interface ComponentsProps { userType?: UserTypeEnum; - peopleGroupSupportType?: PeopleGroupSupportTypeEnum; - salaryRequestEligible?: boolean; + usStaffGroup?: UsStaffGroupEnum; } const Components: React.FC = ({ userType = UserTypeEnum.NonCru, - peopleGroupSupportType = PeopleGroupSupportTypeEnum.SupportedRmo, - salaryRequestEligible = true, + usStaffGroup = UsStaffGroupEnum.SeniorStaff, }) => ( mocks={{ - GetUser: { user: { userType } }, - Hcm: { - hcm: [ - { - salaryRequestEligible, - staffInfo: { peopleGroupSupportType }, - }, - ], - }, + GetUser: { user: { userType, usStaffGroup } }, }} > @@ -56,12 +41,11 @@ describe('GoalCalculator page', () => { ).toBeInTheDocument(); }); - it('should show limited access for a US Staff user whose peopleGroupSupportType is Designation (PDS)', async () => { + it('should show limited access for a US Staff user whose usStaffGroup is PaidWithDesignation (PDS)', async () => { const { findByText } = render( , ); diff --git a/pages/accountLists/[accountListId]/hrTools/mhaCalculator/index.page.test.tsx b/pages/accountLists/[accountListId]/hrTools/mhaCalculator/index.page.test.tsx index 9307bb27e1..a0a8523b13 100644 --- a/pages/accountLists/[accountListId]/hrTools/mhaCalculator/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/mhaCalculator/index.page.test.tsx @@ -9,60 +9,32 @@ import { beforeTestResizeObserver, } from '__tests__/util/windowResizeObserver'; import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers'; -import { StaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccount.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; -import { - AssignmentStatusEnum, - PeopleGroupSupportTypeEnum, - UserPersonTypeEnum, - UserTypeEnum, -} from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import theme from 'src/theme'; import MinisterHousingAllowancePage, { getServerSideProps } from './index.page'; const mutationSpy = jest.fn(); - -const mocks = { - StaffAccount: { - staffAccount: { - id: '12345', - name: 'Test Account', - }, - }, - Hcm: { - hcm: [ - { - mhaEit: { - mhaEligibility: true, - }, - staffInfo: { - preferredName: 'Test User', - peopleGroupSupportType: PeopleGroupSupportTypeEnum.SupportedRmo, - userPersonType: UserPersonTypeEnum.EmployeeStaff, - assignmentStatus: AssignmentStatusEnum.ActivePayrollEligible, - }, - }, - ], - }, -}; interface ComponentProps { userType?: UserTypeEnum; + usStaffGroup?: UsStaffGroupEnum; + staffAccountId?: string; } const Components: React.FC = ({ userType = UserTypeEnum.UsStaff, + usStaffGroup = UsStaffGroupEnum.SeniorStaff, + staffAccountId = '12345', }) => ( mocks={{ - ...mocks, GetUser: { - user: { userType }, + user: { userType, usStaffGroup, staffAccountId }, }, }} onCall={mutationSpy} @@ -109,15 +81,17 @@ describe('MHA Calculation Page', () => { it('renders no staff account page when no staff account', async () => { const mockNoStaffAccount = { - StaffAccount: { - staffAccount: null, + GetUser: { + user: { + staffAccountId: null, + }, }, }; const { findByText } = render( mocks={mockNoStaffAccount} onCall={mutationSpy} diff --git a/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.test.tsx b/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.test.tsx index 0f4d353826..cdb0063775 100644 --- a/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/partnerReminders/index.page.test.tsx @@ -9,38 +9,31 @@ import { beforeTestResizeObserver, } from '__tests__/util/windowResizeObserver'; import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers'; -import { StaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccount.generated'; +import { GetUserQuery } from 'src/components/User/GetUser.generated'; import { UserTypeEnum } from 'src/graphql/types.generated'; import theme from 'src/theme'; import PartnerRemindersReportPage, { getServerSideProps } from './index.page'; const mutationSpy = jest.fn(); -const mocks = { - StaffAccount: { - staffAccount: { - id: '12345', - name: 'Test Account', - }, - }, -}; interface ComponentProps { userType?: UserTypeEnum; + staffAccountId?: string; } const Components: React.FC = ({ userType = UserTypeEnum.UsStaff, + staffAccountId = '12345', }) => ( mocks={{ - ...mocks, GetUser: { - user: { userType }, + user: { userType, staffAccountId }, }, }} onCall={mutationSpy} @@ -84,15 +77,17 @@ describe('Partner Reminders Report Page', () => { it('renders no staff account page when no staff account', async () => { const mockNoStaffAccount = { - StaffAccount: { - staffAccount: null, + GetUser: { + user: { + staffAccountId: null, + }, }, }; const { findByText } = render( mocks={mockNoStaffAccount} onCall={mutationSpy} diff --git a/pages/accountLists/[accountListId]/hrTools/pdsGoalCalculator/[pdsGoalId].page.test.tsx b/pages/accountLists/[accountListId]/hrTools/pdsGoalCalculator/[pdsGoalId].page.test.tsx index 5e7e4607ad..fa3771f839 100644 --- a/pages/accountLists/[accountListId]/hrTools/pdsGoalCalculator/[pdsGoalId].page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/pdsGoalCalculator/[pdsGoalId].page.test.tsx @@ -1,38 +1,50 @@ import React from 'react'; +import { ThemeProvider } from '@mui/material/styles'; +import { SnackbarProvider } from 'notistack'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { render } from '__tests__/util/testingLibraryReactMock'; import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers'; -import { PdsGoalCalculatorTestWrapper } from 'src/components/HrTools/PdsGoalCalculator/PdsGoalCalculatorTestWrapper'; -import { HcmQuery } from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; +import { PdsGoalCalculationQuery } from 'src/components/HrTools/PdsGoalCalculator/GoalsList/PdsGoalCalculations.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; -import { UserTypeEnum } from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; +import theme from 'src/theme'; import { PdsGoalCalculatorPage, getServerSideProps } from './[pdsGoalId].page'; interface AccessComponentsProps { userType?: UserTypeEnum; - designationSupportCalculatorEligible?: boolean; + usStaffGroup?: UsStaffGroupEnum; } const AccessComponents: React.FC = ({ userType = UserTypeEnum.UsStaff, - designationSupportCalculatorEligible = true, + usStaffGroup = UsStaffGroupEnum.PaidWithDesignation, }) => ( - - - mocks={{ - GetUser: { user: { userType } }, - Hcm: { - hcm: [{ designationSupportCalculatorEligible }], - }, + + - - - + + + mocks={{ + GetUser: { user: { userType, usStaffGroup } }, + PdsGoalCalculation: { + designationSupportCalculation: { + updatedAt: '2024-01-01T00:00:00.000Z', + }, + }, + }} + > + + + + + ); describe('[pdsGoalId] page', () => { @@ -50,9 +62,9 @@ describe('[pdsGoalId] page', () => { ).toBeInTheDocument(); }); - it('should show limited access when designationSupportCalculatorEligible is false', async () => { + it('should show limited access when incorrect usStaffGroup', async () => { const { findByText } = render( - , + , ); expect( @@ -61,21 +73,7 @@ describe('[pdsGoalId] page', () => { }); it('renders Saving indicator for an eligible US Staff user', async () => { - const { findByText } = render( - - withProvider={false} - userMock={{ user: { userType: UserTypeEnum.UsStaff } }} - extraMocks={{ - Hcm: { - hcm: [{ designationSupportCalculatorEligible: true }], - }, - }} - > - - , - ); + const { findByText } = render(); expect(await findByText(/Last saved/)).toBeInTheDocument(); }); diff --git a/pages/accountLists/[accountListId]/reports/mpgaIncomeExpenses/index.page.test.tsx b/pages/accountLists/[accountListId]/reports/mpgaIncomeExpenses/index.page.test.tsx index 335a57ec88..5f029587b7 100644 --- a/pages/accountLists/[accountListId]/reports/mpgaIncomeExpenses/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/reports/mpgaIncomeExpenses/index.page.test.tsx @@ -8,7 +8,6 @@ import { beforeTestResizeObserver, } from '__tests__/util/windowResizeObserver'; import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers'; -import { StaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccount.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; import { UserTypeEnum } from 'src/graphql/types.generated'; import theme from 'src/theme'; @@ -16,29 +15,22 @@ import MPGAReportPage, { getServerSideProps } from './index.page'; const mutationSpy = jest.fn(); -const mockStaffAccount = { - StaffAccount: { - staffAccount: { - id: '12345', - name: 'Test Account', - }, - }, -}; - interface ComponentProps { userType?: UserTypeEnum; + staffAccountId?: string; } -const Components = ({ userType = UserTypeEnum.UsStaff }: ComponentProps) => ( +const Components = ({ + userType = UserTypeEnum.UsStaff, + staffAccountId = '12345', +}: ComponentProps) => ( mocks={{ - ...mockStaffAccount, - GetUser: { user: { userType } }, + GetUser: { user: { userType, staffAccountId } }, }} onCall={mutationSpy} > @@ -82,15 +74,17 @@ describe('MPGA Report Page', () => { it('renders no staff account page when no staff account', async () => { const mockNoStaffAccount = { - StaffAccount: { - staffAccount: null, + GetUser: { + user: { + staffAccountId: null, + }, }, }; const { findByText } = render( mocks={mockNoStaffAccount} onCall={mutationSpy} diff --git a/src/components/HrTools/StaffSavingFund/StaffSavingFundLayout.test.tsx b/src/components/HrTools/StaffSavingFund/StaffSavingFundLayout.test.tsx index 8d9ae5b143..d79ac8c23c 100644 --- a/src/components/HrTools/StaffSavingFund/StaffSavingFundLayout.test.tsx +++ b/src/components/HrTools/StaffSavingFund/StaffSavingFundLayout.test.tsx @@ -103,14 +103,16 @@ describe('StaffSavingFundLayout', () => { it('renders no staff account page when no staff account', async () => { const mockNoStaffAccount = { - StaffAccount: { - staffAccount: null, + GetUser: { + user: { + staffAccountId: null, + }, }, }; const { findByText } = render( mocks={mockNoStaffAccount} onCall={mutationSpy} diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx index a3b1196225..5cb3cf1d12 100644 --- a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx @@ -7,14 +7,8 @@ import { session } from '__tests__/fixtures/session'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { GetDesignationAccountsQuery } from 'src/components/EditDonationModal/EditDonationModal.generated'; -import { HcmQuery } from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; -import { - AssignmentStatusEnum, - PeopleGroupSupportTypeEnum, - UserPersonTypeEnum, - UserTypeEnum, -} from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import { UserOptionQuery } from 'src/hooks/UserPreference.generated'; import theme from 'src/theme'; import { MultiPageMenu, NavTypeEnum } from './MultiPageMenu'; @@ -499,121 +493,239 @@ describe('MultiPageMenu', () => { }); }); - it('shows hr tools', async () => { - const { findByText, getByText } = render( - - - - mocks={{ - Hcm: { - hcm: [ - { - staffInfo: { - peopleGroupSupportType: - PeopleGroupSupportTypeEnum.SupportedRmo, - userPersonType: UserPersonTypeEnum.EmployeeStaff, - assignmentStatus: - AssignmentStatusEnum.ActivePayrollEligible, - }, - asrEit: { asrEligibility: true }, - salaryRequestEligible: true, - designationSupportCalculatorEligible: true, + describe('HR Tools Menu', () => { + it('shows hr tools for senior staff', async () => { + const { findByText, getByText, queryByText } = render( + + + + mocks={{ + GetUser: { + user: { + userType: UserTypeEnum.UsStaff, + usStaffGroup: UsStaffGroupEnum.SeniorStaff, + staffAccountId: '12345', }, - ], - }, - }} - > - {}} - designationAccounts={[]} - setDesignationAccounts={() => {}} - navType={NavTypeEnum.HrTools} - /> - - - , - ); + }, + }} + > + {}} + designationAccounts={[]} + setDesignationAccounts={() => {}} + navType={NavTypeEnum.HrTools} + /> + + + , + ); + + expect(await findByText('Salary Calculation Form')).toBeInTheDocument(); + expect(getByText('Savings Fund Transfer')).toBeInTheDocument(); + expect(getByText('MPD Goal Calculator')).toBeInTheDocument(); + expect(getByText('MHA Calculation Tool')).toBeInTheDocument(); + expect(getByText('Additional Salary Request')).toBeInTheDocument(); + expect( + queryByText('Paid with Designation Support Goal Calculator'), + ).not.toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); + }); - expect(await findByText('Salary Calculation Form')).toBeInTheDocument(); - expect(getByText('Savings Fund Transfer')).toBeInTheDocument(); - expect(getByText('MPD Goal Calculator')).toBeInTheDocument(); - expect(getByText('MHA Calculation Tool')).toBeInTheDocument(); - expect(getByText('Additional Salary Request')).toBeInTheDocument(); - expect( - getByText('Paid with Designation Support Goal Calculator'), - ).toBeInTheDocument(); - expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); - }); + it('shows hr tools for new staff', async () => { + const { findByText, getByText, queryByText } = render( + + + + mocks={{ + GetUser: { + user: { + userType: UserTypeEnum.UsStaff, + usStaffGroup: UsStaffGroupEnum.NewStaff, + staffAccountId: '12345', + }, + }, + }} + > + {}} + designationAccounts={[]} + setDesignationAccounts={() => {}} + navType={NavTypeEnum.HrTools} + /> + + + , + ); + + expect(await findByText('Additional Salary Request')).toBeInTheDocument(); + expect(queryByText('Salary Calculation Form')).not.toBeInTheDocument(); + expect(getByText('Savings Fund Transfer')).toBeInTheDocument(); + expect(queryByText('MPD Goal Calculator')).not.toBeInTheDocument(); + expect(queryByText('MHA Calculation Tool')).not.toBeInTheDocument(); + expect( + queryByText('Paid with Designation Support Goal Calculator'), + ).not.toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); + }); - it('hides MPD Goal Calculator when salaryRequestEligible is false', async () => { - const { findByText, queryByText } = render( - - - - mocks={{ - Hcm: { - hcm: [ - { - salaryRequestEligible: false, - designationSupportCalculatorEligible: true, + it('shows hr tools for national expat staff', async () => { + const { findByText, getByText, queryByText } = render( + + + + mocks={{ + GetUser: { + user: { + userType: UserTypeEnum.UsStaff, + usStaffGroup: UsStaffGroupEnum.NationalExpat, + staffAccountId: '12345', }, - ], - }, - }} - > - {}} - designationAccounts={[]} - setDesignationAccounts={() => {}} - navType={NavTypeEnum.HrTools} - /> - - - , - ); + }, + }} + > + {}} + designationAccounts={[]} + setDesignationAccounts={() => {}} + navType={NavTypeEnum.HrTools} + /> + + + , + ); + + expect(await findByText('Salary Calculation Form')).toBeInTheDocument(); + expect(getByText('Savings Fund Transfer')).toBeInTheDocument(); + expect(queryByText('MPD Goal Calculator')).not.toBeInTheDocument(); + expect(getByText('MHA Calculation Tool')).toBeInTheDocument(); + expect(getByText('Additional Salary Request')).toBeInTheDocument(); + expect( + queryByText('Paid with Designation Support Goal Calculator'), + ).not.toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); + }); - expect( - await findByText('Paid with Designation Support Goal Calculator'), - ).toBeInTheDocument(); - expect(queryByText('MPD Goal Calculator')).not.toBeInTheDocument(); - }); + it('shows hr tools for paid with designation', async () => { + const { findByText, queryByText } = render( + + + + mocks={{ + GetUser: { + user: { + userType: UserTypeEnum.UsStaff, + usStaffGroup: UsStaffGroupEnum.PaidWithDesignation, + staffAccountId: null, + }, + }, + }} + > + {}} + designationAccounts={[]} + setDesignationAccounts={() => {}} + navType={NavTypeEnum.HrTools} + /> + + + , + ); + + expect( + await findByText('Paid with Designation Support Goal Calculator'), + ).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('Ministry Partner Reminders')).not.toBeInTheDocument(); + }); - it('hides PDS Goal Calculator when designationSupportCalculatorEligible is false', async () => { - const { findByText, queryByText } = render( - - - - mocks={{ - Hcm: { - hcm: [ - { - salaryRequestEligible: true, - designationSupportCalculatorEligible: false, + it('shows hr tools for part time field staff', async () => { + const { findByText, getByText, queryByText } = render( + + + + mocks={{ + GetUser: { + user: { + userType: UserTypeEnum.UsStaff, + usStaffGroup: UsStaffGroupEnum.PartTimeFieldStaff, + staffAccountId: '12345', }, - ], - }, - }} - > - {}} - designationAccounts={[]} - setDesignationAccounts={() => {}} - navType={NavTypeEnum.HrTools} - /> - - - , - ); + }, + }} + > + {}} + designationAccounts={[]} + setDesignationAccounts={() => {}} + navType={NavTypeEnum.HrTools} + /> + + + , + ); + + expect(await findByText('Savings Fund Transfer')).toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); + expect( + queryByText('Paid with Designation Support Goal Calculator'), + ).not.toBeInTheDocument(); + expect(queryByText('Salary Calculation Form')).not.toBeInTheDocument(); + expect(queryByText('Additional Salary Request')).not.toBeInTheDocument(); + expect(queryByText('MPD Goal Calculator')).not.toBeInTheDocument(); + expect(queryByText('MHA Calculation Tool')).not.toBeInTheDocument(); + }); - expect(await findByText('MPD Goal Calculator')).toBeInTheDocument(); - expect( - queryByText('Paid with Designation Support Goal Calculator'), - ).not.toBeInTheDocument(); + it('shows hr tools for interns', async () => { + const { findByText, getByText, queryByText } = render( + + + + mocks={{ + GetUser: { + user: { + userType: UserTypeEnum.UsStaff, + usStaffGroup: UsStaffGroupEnum.Intern, + staffAccountId: '12345', + }, + }, + }} + > + {}} + designationAccounts={[]} + setDesignationAccounts={() => {}} + navType={NavTypeEnum.HrTools} + /> + + + , + ); + + expect(await findByText('Savings Fund Transfer')).toBeInTheDocument(); + expect(getByText('Ministry Partner Reminders')).toBeInTheDocument(); + expect( + queryByText('Paid with Designation Support Goal Calculator'), + ).not.toBeInTheDocument(); + expect(queryByText('Salary Calculation Form')).not.toBeInTheDocument(); + expect(queryByText('Additional Salary Request')).not.toBeInTheDocument(); + expect(queryByText('MPD Goal Calculator')).not.toBeInTheDocument(); + expect(queryByText('MHA Calculation Tool')).not.toBeInTheDocument(); + }); }); }); diff --git a/src/components/Shared/UserTypeAccess/UserTypeAccess.test.tsx b/src/components/Shared/UserTypeAccess/UserTypeAccess.test.tsx index ce7f22d7f1..101285c06b 100644 --- a/src/components/Shared/UserTypeAccess/UserTypeAccess.test.tsx +++ b/src/components/Shared/UserTypeAccess/UserTypeAccess.test.tsx @@ -3,14 +3,8 @@ import { ThemeProvider } from '@mui/material/styles'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { render } from '__tests__/util/testingLibraryReactMock'; -import { HcmQuery } from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; -import { StaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccount.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; -import { - PeopleGroupSupportTypeEnum, - UserPersonTypeEnum, - UserTypeEnum, -} from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import theme from 'src/theme'; import { RequiredUserGroupEnum, UserTypeAccess } from './UserTypeAccess'; @@ -19,50 +13,25 @@ const id = 'staff-1'; interface TestComponentProps { requireStaffAccount?: boolean; userType?: UserTypeEnum; + usStaffGroup?: UsStaffGroupEnum; staffAccountId?: string | null; requireUserGroups?: RequiredUserGroupEnum; - asrEligible?: boolean; - salaryRequestEligible?: boolean; - designationSupportCalculatorEligible?: boolean; - peopleGroupSupportType?: PeopleGroupSupportTypeEnum; - userPersonType?: UserPersonTypeEnum; } const TestComponent: React.FC = ({ requireStaffAccount, userType = UserTypeEnum.UsStaff, + usStaffGroup = UsStaffGroupEnum.PartTimeFieldStaff, staffAccountId = id, requireUserGroups, - asrEligible = true, - salaryRequestEligible = true, - designationSupportCalculatorEligible = true, - peopleGroupSupportType = PeopleGroupSupportTypeEnum.SupportedRmo, - userPersonType = UserPersonTypeEnum.EmployeeHourly, }) => ( mocks={{ - Hcm: { - hcm: [ - { - asrEit: { asrEligibility: asrEligible }, - salaryRequestEligible, - designationSupportCalculatorEligible, - staffInfo: { peopleGroupSupportType, userPersonType }, - }, - ], - }, - GetUser: { user: { userType } }, - StaffAccount: { - staffAccount: staffAccountId - ? { id: staffAccountId, name: 'Test Account' } - : null, - }, + GetUser: { user: { userType, usStaffGroup, staffAccountId } }, }} > { const { findByRole, getByText } = render( , ); @@ -124,7 +93,7 @@ describe('UserTypeAccess', () => { const { findByRole, getByText } = render( , ); @@ -144,7 +113,7 @@ describe('UserTypeAccess', () => { const { findByRole } = render( , ); expect( @@ -158,7 +127,7 @@ describe('UserTypeAccess', () => { const { findByRole } = render( , ); expect( @@ -172,7 +141,7 @@ describe('UserTypeAccess', () => { const { findByText } = render( , ); expect(await findByText('Test Content')).toBeInTheDocument(); @@ -228,34 +197,4 @@ describe('UserTypeAccess', () => { getByText(/something went wrong while loading your account information/i), ).toBeInTheDocument(); }); - - it('should render LimitedAccess with user error when there is an error loading the staff account', async () => { - const { findByRole, getByText } = render( - - - { - throw new Error('Staff account error'); - }, - }, - }} - > - -
Test Content
-
-
-
-
, - ); - - expect( - await findByRole('heading', { name: 'Unable to load this page' }), - ).toBeInTheDocument(); - expect( - getByText(/something went wrong while loading your account information/i), - ).toBeInTheDocument(); - }); }); diff --git a/src/components/Shared/UserTypeAccess/UserTypeAccess.tsx b/src/components/Shared/UserTypeAccess/UserTypeAccess.tsx index 4d69223c94..7dd9015fac 100644 --- a/src/components/Shared/UserTypeAccess/UserTypeAccess.tsx +++ b/src/components/Shared/UserTypeAccess/UserTypeAccess.tsx @@ -1,9 +1,7 @@ import Loading from 'src/components/Loading'; -import { useGetUserQuery } from 'src/components/User/GetUser.generated'; import { UserTypeEnum } from 'src/graphql/types.generated'; -import { useUsStaffGroups } from 'src/hooks/useUsStaffGroups'; +import { useIneligibleByGroup } from 'src/hooks/useIneligibleByGroup'; import { LimitedAccess } from '../LimitedAccess/LimitedAccess'; -import { useStaffAccountQuery } from '../StaffAccount/StaffAccount.generated'; export enum RequiredUserGroupEnum { Asr = 'asr', @@ -28,25 +26,17 @@ export const UserTypeAccess: React.FC = ({ alwaysAllow, requireUserGroups, }) => { - const { data, loading: userLoading, error: userError } = useGetUserQuery(); - const { - data: staffAccountData, - loading: staffAccountLoading, - error: staffAccountError, - } = useStaffAccountQuery({ - skip: !requireStaffAccount, - }); - - // Only run HCM query if we are using an HCM report - const skip = !requireUserGroups; const { inAsrIneligibleGroup, inSalaryCalcIneligibleGroup, inMhaIneligibleGroup, inMpdGoalCalcIneligibleGroup, inPdsGoalCalcIneligibleGroup, - loading: hcmLoading, - } = useUsStaffGroups(skip); + userType, + hasNoStaffAccount, + userLoading, + userError, + } = useIneligibleByGroup(); const ineligibleByGroup: Record = { [RequiredUserGroupEnum.Asr]: inAsrIneligibleGroup, @@ -56,8 +46,6 @@ export const UserTypeAccess: React.FC = ({ [RequiredUserGroupEnum.PdsGoalCalc]: inPdsGoalCalcIneligibleGroup, }; - const userType = data?.user.userType; - const limitedAccess = (userType && userType !== requiredUserType) || (requireUserGroups && ineligibleByGroup[requireUserGroups]); @@ -71,7 +59,7 @@ export const UserTypeAccess: React.FC = ({ return ; } - if ((userLoading && !userType) || hcmLoading) { + if (userLoading && !userType) { return ; } @@ -80,13 +68,7 @@ export const UserTypeAccess: React.FC = ({ } if (requireStaffAccount) { - if (staffAccountError) { - return ; - } - if (staffAccountLoading && !staffAccountData) { - return ; - } - if (!staffAccountData?.staffAccount?.id) { + if (hasNoStaffAccount) { return ; } } diff --git a/src/components/User/GetUser.graphql b/src/components/User/GetUser.graphql index b3cc71b06d..e530b6a12b 100644 --- a/src/components/User/GetUser.graphql +++ b/src/components/User/GetUser.graphql @@ -12,5 +12,6 @@ query GetUser { staffAccountId primaryDesignation userType + usStaffGroup } } diff --git a/src/hooks/useHrToolsNavItems.ts b/src/hooks/useHrToolsNavItems.ts index 5d45fe46b7..d6fb3b1113 100644 --- a/src/hooks/useHrToolsNavItems.ts +++ b/src/hooks/useHrToolsNavItems.ts @@ -1,26 +1,25 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import { useIneligibleByGroup } from './useIneligibleByGroup'; import { NavItems } from './useReportNavItems'; -import { useUsStaffGroups } from './useUsStaffGroups'; export function useHrToolsNavItems(): { items: NavItems[]; loading: boolean; } { const { t } = useTranslation(); - const { inAsrIneligibleGroup, inSalaryCalcIneligibleGroup, inMhaIneligibleGroup, inMpdGoalCalcIneligibleGroup, inPdsGoalCalcIneligibleGroup, - hasNoHcmData, - loading: hcmLoading, - } = useUsStaffGroups(); + hasNoStaffAccount, + userLoading, + } = useIneligibleByGroup(); const items = useMemo(() => { - if (hcmLoading) { + if (userLoading) { return []; } @@ -33,7 +32,7 @@ export function useHrToolsNavItems(): { { id: 'staffSavingFund', title: t('Savings Fund Transfer'), - hideItem: hasNoHcmData, + hideItem: hasNoStaffAccount, }, { id: 'goalCalculator', @@ -58,7 +57,7 @@ export function useHrToolsNavItems(): { { id: 'partnerReminders', title: t('Ministry Partner Reminders'), - hideItem: hasNoHcmData, + hideItem: hasNoStaffAccount, }, ].filter((item) => !item.hideItem); }, [ @@ -68,9 +67,9 @@ export function useHrToolsNavItems(): { inMhaIneligibleGroup, inMpdGoalCalcIneligibleGroup, inPdsGoalCalcIneligibleGroup, - hasNoHcmData, - hcmLoading, + userLoading, + hasNoStaffAccount, ]); - return { items, loading: hcmLoading }; + return { items, loading: userLoading }; } diff --git a/src/hooks/useIneligibleByGroup.test.tsx b/src/hooks/useIneligibleByGroup.test.tsx new file mode 100644 index 0000000000..accf86efca --- /dev/null +++ b/src/hooks/useIneligibleByGroup.test.tsx @@ -0,0 +1,166 @@ +import { ReactElement } from 'react'; +import { waitFor } from '@testing-library/dom'; +import { renderHook } from '@testing-library/react-hooks'; +import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; +import { GetUserQuery } from 'src/components/User/GetUser.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; +import { useIneligibleByGroup } from './useIneligibleByGroup'; + +interface MockUser { + usStaffGroup?: UsStaffGroupEnum | null; + userType?: UserTypeEnum; + staffAccountId?: string | null; +} + +const renderUseIneligibleByGroup = (user: MockUser = {}) => + renderHook(() => useIneligibleByGroup(), { + wrapper: ({ children }: { children: ReactElement }) => ( + + mocks={{ + GetUser: { + user: { + userType: UserTypeEnum.UsStaff, + staffAccountId: 'staff-account-1', + ...user, + }, + }, + }} + > + {children} + + ), + }); + +describe('useIneligibleByGroup', () => { + it('reports loading before the user query resolves', () => { + const { result } = renderUseIneligibleByGroup(); + + expect(result.current.userLoading).toBe(true); + expect(result.current.userError).toBeUndefined(); + }); + + it('surfaces a query error', async () => { + const { result } = renderHook(() => useIneligibleByGroup(), { + wrapper: ({ children }: { children: ReactElement }) => ( + { + throw new Error('User fetch failed'); + }, + }, + }} + > + {children} + + ), + }); + + await waitFor(() => expect(result.current.userError).toBeDefined()); + expect(result.current.userLoading).toBe(false); + }); + + it('passes through userType and hasNoStaffAccount', async () => { + const { result } = renderUseIneligibleByGroup({ + userType: UserTypeEnum.UsStaff, + staffAccountId: null, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.userType).toBe(UserTypeEnum.UsStaff); + expect(result.current.hasNoStaffAccount).toBe(true); + }); + + describe('ineligibility by staff group', () => { + it('senior staff eligibility', async () => { + const { result } = renderUseIneligibleByGroup({ + usStaffGroup: UsStaffGroupEnum.SeniorStaff, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.inAsrIneligibleGroup).toBe(false); + expect(result.current.inSalaryCalcIneligibleGroup).toBe(false); + expect(result.current.inMhaIneligibleGroup).toBe(false); + expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(false); + expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); + }); + + it('new staff eligibility', async () => { + const { result } = renderUseIneligibleByGroup({ + usStaffGroup: UsStaffGroupEnum.NewStaff, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.inAsrIneligibleGroup).toBe(false); + expect(result.current.inSalaryCalcIneligibleGroup).toBe(true); + expect(result.current.inMhaIneligibleGroup).toBe(true); + expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(true); + expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); + }); + + it('part time field staff eligibility', async () => { + const { result } = renderUseIneligibleByGroup({ + usStaffGroup: UsStaffGroupEnum.PartTimeFieldStaff, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.inAsrIneligibleGroup).toBe(true); + expect(result.current.inSalaryCalcIneligibleGroup).toBe(true); + expect(result.current.inMhaIneligibleGroup).toBe(true); + expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(true); + expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); + }); + + it('paid with designation eligibility', async () => { + const { result } = renderUseIneligibleByGroup({ + usStaffGroup: UsStaffGroupEnum.PaidWithDesignation, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.inAsrIneligibleGroup).toBe(true); + expect(result.current.inSalaryCalcIneligibleGroup).toBe(true); + expect(result.current.inMhaIneligibleGroup).toBe(true); + expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(true); + expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(false); + }); + + it('intern eligibility', async () => { + const { result } = renderUseIneligibleByGroup({ + usStaffGroup: UsStaffGroupEnum.Intern, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.inAsrIneligibleGroup).toBe(true); + expect(result.current.inSalaryCalcIneligibleGroup).toBe(true); + expect(result.current.inMhaIneligibleGroup).toBe(true); + expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(true); + expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); + }); + + it('national expat eligibility', async () => { + const { result } = renderUseIneligibleByGroup({ + usStaffGroup: UsStaffGroupEnum.NationalExpat, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.inAsrIneligibleGroup).toBe(false); + expect(result.current.inSalaryCalcIneligibleGroup).toBe(false); + expect(result.current.inMhaIneligibleGroup).toBe(false); + expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(true); + expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); + }); + + it('no us staff group eligibility', async () => { + const { result } = renderUseIneligibleByGroup({ + usStaffGroup: null, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.inAsrIneligibleGroup).toBe(true); + expect(result.current.inSalaryCalcIneligibleGroup).toBe(true); + expect(result.current.inMhaIneligibleGroup).toBe(true); + expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(true); + expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); + }); + }); +}); diff --git a/src/hooks/useIneligibleByGroup.ts b/src/hooks/useIneligibleByGroup.ts new file mode 100644 index 0000000000..22f46a9253 --- /dev/null +++ b/src/hooks/useIneligibleByGroup.ts @@ -0,0 +1,49 @@ +import { useMemo } from 'react'; +import { useGetUserQuery } from 'src/components/User/GetUser.generated'; +import { UsStaffGroupEnum } from 'src/graphql/types.generated'; + +export function useIneligibleByGroup() { + const { data, loading: userLoading, error: userError } = useGetUserQuery(); + + const usStaffGroup = data?.user.usStaffGroup; + const userType = data?.user.userType; + const hasNoStaffAccount = !data?.user.staffAccountId; + + const isSeniorStaff = usStaffGroup === UsStaffGroupEnum.SeniorStaff; + const isNationalExpat = usStaffGroup === UsStaffGroupEnum.NationalExpat; + + const inAsrIneligibleGroup = + !isSeniorStaff && + usStaffGroup !== UsStaffGroupEnum.NewStaff && + !isNationalExpat; + const inSalaryCalcIneligibleGroup = !isSeniorStaff && !isNationalExpat; + const inMhaIneligibleGroup = !isSeniorStaff && !isNationalExpat; + const inMpdGoalCalcIneligibleGroup = !isSeniorStaff; + const inPdsGoalCalcIneligibleGroup = + usStaffGroup !== UsStaffGroupEnum.PaidWithDesignation; + + return useMemo( + () => ({ + inAsrIneligibleGroup, + inSalaryCalcIneligibleGroup, + inMhaIneligibleGroup, + inMpdGoalCalcIneligibleGroup, + inPdsGoalCalcIneligibleGroup, + userType, + hasNoStaffAccount, + userLoading, + userError, + }), + [ + inAsrIneligibleGroup, + inSalaryCalcIneligibleGroup, + inMhaIneligibleGroup, + inMpdGoalCalcIneligibleGroup, + inPdsGoalCalcIneligibleGroup, + userType, + hasNoStaffAccount, + userLoading, + userError, + ], + ); +} diff --git a/src/hooks/useNavPages.tsx b/src/hooks/useNavPages.tsx index e3fd2a44bc..4ef29721e9 100644 --- a/src/hooks/useNavPages.tsx +++ b/src/hooks/useNavPages.tsx @@ -122,7 +122,8 @@ export function useNavPages(coachingAccountCount: boolean, isSearch = false) { showInNav: true, hideTab: (!!data && !showTab) || - (!hrToolsLoading && hrToolsItems.length === 0), + hrToolsLoading || + hrToolsItems.length === 0, }, ]), { diff --git a/src/hooks/useUsStaffGroups.test.tsx b/src/hooks/useUsStaffGroups.test.tsx deleted file mode 100644 index d172922236..0000000000 --- a/src/hooks/useUsStaffGroups.test.tsx +++ /dev/null @@ -1,384 +0,0 @@ -import React, { ReactElement } from 'react'; -import { renderHook, waitFor } from '@testing-library/react'; -import { GraphQLError } from 'graphql'; -import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; -import { HcmQuery } from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; -import { - AssignmentStatusEnum, - PeopleGroupSupportTypeEnum, - UserPersonTypeEnum, -} from 'src/graphql/types.generated'; -import { useUsStaffGroups } from './useUsStaffGroups'; - -interface BuildHcmMockOptions { - userAsrEligible?: boolean; - userSalaryRequestEligible?: boolean; - userMhaEligibility?: boolean; - userDesignationSupportCalculatorEligible?: boolean; - userPersonType?: UserPersonTypeEnum; - peopleGroupSupportType?: PeopleGroupSupportTypeEnum; - assignmentStatus?: AssignmentStatusEnum; - includeSpouse?: boolean; - spouseAsrEligible?: boolean; - spouseDesignationSupportCalculatorEligible?: boolean; -} - -const mhaEligibleStaffInfo = { - userPersonType: UserPersonTypeEnum.EmployeeStaff, - peopleGroupSupportType: PeopleGroupSupportTypeEnum.SupportedRmo, - assignmentStatus: AssignmentStatusEnum.ActivePayrollEligible, -}; - -const buildHcmMock = ({ - userAsrEligible = true, - userSalaryRequestEligible = true, - userMhaEligibility = true, - userDesignationSupportCalculatorEligible = true, - userPersonType = mhaEligibleStaffInfo.userPersonType, - peopleGroupSupportType = mhaEligibleStaffInfo.peopleGroupSupportType, - assignmentStatus = mhaEligibleStaffInfo.assignmentStatus, - includeSpouse = false, - spouseAsrEligible = true, - spouseDesignationSupportCalculatorEligible = true, -}: BuildHcmMockOptions = {}): HcmQuery => { - const entries = [ - { - asrEit: { asrEligibility: userAsrEligible }, - salaryRequestEligible: userSalaryRequestEligible, - designationSupportCalculatorEligible: - userDesignationSupportCalculatorEligible, - mhaEit: { mhaEligibility: userMhaEligibility }, - staffInfo: { - userPersonType, - peopleGroupSupportType, - assignmentStatus, - }, - }, - ]; - if (includeSpouse) { - entries.push({ - asrEit: { asrEligibility: spouseAsrEligible }, - salaryRequestEligible: true, - designationSupportCalculatorEligible: - spouseDesignationSupportCalculatorEligible, - mhaEit: { mhaEligibility: true }, - staffInfo: { ...mhaEligibleStaffInfo }, - }); - } - return { hcm: entries } as HcmQuery; -}; - -const renderUseUsStaffGroups = (hcmMock: HcmQuery, skip?: boolean) => - renderHook(() => useUsStaffGroups(skip), { - wrapper: ({ children }: { children: ReactElement }) => ( - mocks={{ Hcm: hcmMock }}> - {children} - - ), - }); - -describe('useUsStaffGroups', () => { - it('marks an ASR-eligible, salary-eligible, MHA-eligible, PDS-eligible user as eligible', async () => { - const { result } = renderUseUsStaffGroups(buildHcmMock()); - - await waitFor(() => { - expect(result.current).toEqual({ - inAsrIneligibleGroup: false, - inSalaryCalcIneligibleGroup: false, - inMhaIneligibleGroup: false, - inMpdGoalCalcIneligibleGroup: false, - inPdsGoalCalcIneligibleGroup: false, - hasNoHcmData: false, - loading: false, - }); - }); - }); - - it('marks user as salary-calc and mpd-goal-calc ineligible when salaryRequestEligible is false', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ userSalaryRequestEligible: false }), - ); - - await waitFor(() => { - expect(result.current).toEqual({ - inAsrIneligibleGroup: false, - inSalaryCalcIneligibleGroup: true, - inMhaIneligibleGroup: false, - inMpdGoalCalcIneligibleGroup: true, - inPdsGoalCalcIneligibleGroup: false, - hasNoHcmData: false, - loading: false, - }); - }); - }); - - it('marks user as mha ineligible when userPersonType is not staff', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - userPersonType: UserPersonTypeEnum.EmployeeHourly, - }), - ); - - await waitFor(() => { - expect(result.current).toEqual({ - inAsrIneligibleGroup: false, - inSalaryCalcIneligibleGroup: false, - inMhaIneligibleGroup: true, - inMpdGoalCalcIneligibleGroup: false, - inPdsGoalCalcIneligibleGroup: false, - hasNoHcmData: false, - loading: false, - }); - }); - }); - - it('marks user as mha ineligible when peopleGroupSupportType is not SupportedRmo', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - peopleGroupSupportType: PeopleGroupSupportTypeEnum.SupportedNonRmo, - }), - ); - - await waitFor(() => { - expect(result.current.inMhaIneligibleGroup).toBe(true); - }); - }); - - it('marks user as mha ineligible when assignmentStatus is not ActivePayrollEligible', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - assignmentStatus: AssignmentStatusEnum.ActiveNoPayroll, - }), - ); - - await waitFor(() => { - expect(result.current.inMhaIneligibleGroup).toBe(true); - }); - }); - - it('keeps user MHA-eligible when staffInfo qualifies but mhaEligibility is false (unmet IBS courses)', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ userMhaEligibility: false }), - ); - - await waitFor(() => { - expect(result.current).toEqual({ - inAsrIneligibleGroup: false, - inSalaryCalcIneligibleGroup: false, - inMhaIneligibleGroup: false, - inMpdGoalCalcIneligibleGroup: false, - inPdsGoalCalcIneligibleGroup: false, - hasNoHcmData: false, - loading: false, - }); - }); - }); - - it('marks user as mha eligible when userPersonType is EmployeeStaffNonRmoSpouse', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - userPersonType: UserPersonTypeEnum.EmployeeStaffNonRmoSpouse, - }), - ); - - await waitFor(() => { - expect(result.current.inMhaIneligibleGroup).toBe(false); - }); - }); - - it('marks user as ASR ineligible when user and spouse are both ASR-ineligible', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - userAsrEligible: false, - includeSpouse: true, - spouseAsrEligible: false, - }), - ); - - await waitFor(() => { - expect(result.current.inAsrIneligibleGroup).toBe(true); - }); - }); - - it('marks user as ASR ineligible when there is no spouse and user is ASR-ineligible', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ userAsrEligible: false }), - ); - - await waitFor(() => { - expect(result.current.inAsrIneligibleGroup).toBe(true); - }); - }); - - it('falls back to the spouse for ASR eligibility when the logged-in user is not ASR-eligible', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - userAsrEligible: false, - includeSpouse: true, - spouseAsrEligible: true, - }), - ); - - await waitFor(() => { - expect(result.current.inAsrIneligibleGroup).toBe(false); - }); - }); - - it('uses the logged-in user for salary calc even when a spouse is present', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - userSalaryRequestEligible: false, - includeSpouse: true, - }), - ); - - await waitFor(() => { - expect(result.current.inSalaryCalcIneligibleGroup).toBe(true); - }); - }); - - it('uses the logged-in user for mpd goal calc even when a spouse is present', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - userSalaryRequestEligible: false, - includeSpouse: true, - }), - ); - - await waitFor(() => { - expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(true); - }); - }); - - it('skips the HCM query and returns defaults when skip is true', () => { - const { result } = renderUseUsStaffGroups(buildHcmMock(), true); - - expect(result.current).toEqual({ - inAsrIneligibleGroup: false, - inSalaryCalcIneligibleGroup: false, - inMhaIneligibleGroup: false, - inMpdGoalCalcIneligibleGroup: false, - inPdsGoalCalcIneligibleGroup: false, - hasNoHcmData: false, - loading: false, - }); - }); - - it('returns eligible defaults when the HCM array is empty', async () => { - const { result } = renderUseUsStaffGroups({ hcm: [] } as HcmQuery); - - await waitFor(() => { - expect(result.current).toEqual({ - inAsrIneligibleGroup: false, - inSalaryCalcIneligibleGroup: false, - inMhaIneligibleGroup: false, - inMpdGoalCalcIneligibleGroup: false, - inPdsGoalCalcIneligibleGroup: false, - hasNoHcmData: false, - loading: false, - }); - }); - }); - - it('treats NO_STAFF_ACCOUNT error as ineligible for ASR, salary calc, and MHA but keeps MPD and PDS goal calc eligible', async () => { - const { result } = renderHook(() => useUsStaffGroups(), { - wrapper: ({ children }: { children: ReactElement }) => ( - { - throw new GraphQLError('Staff account id not found', { - extensions: { code: 'NO_STAFF_ACCOUNT' }, - }); - }, - }, - }} - > - {children} - - ), - }); - - await waitFor(() => { - expect(result.current).toEqual({ - inAsrIneligibleGroup: true, - inSalaryCalcIneligibleGroup: true, - inMhaIneligibleGroup: true, - inMpdGoalCalcIneligibleGroup: false, - inPdsGoalCalcIneligibleGroup: false, - hasNoHcmData: true, - loading: false, - }); - }); - }); - - it('treats HCM_PERSON_NOT_FOUND error as ineligible for ASR, salary calc, MHA, MPD goal calc, and PDS goal calc', async () => { - const { result } = renderHook(() => useUsStaffGroups(), { - wrapper: ({ children }: { children: ReactElement }) => ( - { - throw new GraphQLError('Person not found', { - extensions: { code: 'HCM_PERSON_NOT_FOUND' }, - }); - }, - }, - }} - > - {children} - - ), - }); - - await waitFor(() => { - expect(result.current).toEqual({ - inAsrIneligibleGroup: true, - inSalaryCalcIneligibleGroup: true, - inMhaIneligibleGroup: true, - inMpdGoalCalcIneligibleGroup: true, - inPdsGoalCalcIneligibleGroup: true, - hasNoHcmData: true, - loading: false, - }); - }); - }); - - it('marks user as PDS goal calc ineligible when user and spouse are both PDS-ineligible', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - userDesignationSupportCalculatorEligible: false, - includeSpouse: true, - spouseDesignationSupportCalculatorEligible: false, - }), - ); - - await waitFor(() => { - expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); - }); - }); - - it('marks user as PDS goal calc ineligible when there is no spouse and user is PDS-ineligible', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ userDesignationSupportCalculatorEligible: false }), - ); - - await waitFor(() => { - expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); - }); - }); - - it('falls back to the spouse for PDS goal calc eligibility when the logged-in user is not PDS-eligible', async () => { - const { result } = renderUseUsStaffGroups( - buildHcmMock({ - userDesignationSupportCalculatorEligible: false, - includeSpouse: true, - spouseDesignationSupportCalculatorEligible: true, - }), - ); - - await waitFor(() => { - expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(false); - }); - }); -}); diff --git a/src/hooks/useUsStaffGroups.ts b/src/hooks/useUsStaffGroups.ts deleted file mode 100644 index a649de8236..0000000000 --- a/src/hooks/useUsStaffGroups.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { useMemo } from 'react'; -import { - HcmQuery, - useHcmQuery, -} from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; -import { - AssignmentStatusEnum, - PeopleGroupSupportTypeEnum, - UserPersonTypeEnum, -} from 'src/graphql/types.generated'; - -// Hide MHA tab by HR classification only; ignore mhaEligibility (IBS courses are resolvable). -function mhaIneligible(user: HcmQuery['hcm'][number]['staffInfo']) { - const eligibleUserPersonType = - user?.userPersonType === UserPersonTypeEnum.EmployeeStaff || - user?.userPersonType === UserPersonTypeEnum.EmployeeStaffNonRmoSpouse; - const eligibleSupportType = - user?.peopleGroupSupportType === PeopleGroupSupportTypeEnum.SupportedRmo; - const eligibleAssignmentStatus = - user?.assignmentStatus === AssignmentStatusEnum.ActivePayrollEligible; - - const isIneligible = !( - eligibleUserPersonType && - eligibleSupportType && - eligibleAssignmentStatus - ); - - return isIneligible; -} - -/** - * This hook determines whether a US Staff user is in a subgroup that's ineligible for ASR, Salary Calculator, MHA, or the MPD / PDS Goal Calculators (all reports that require HCM). - * The ASR, MHA, and PDS Goal Calculator checks run against the eligible person (logged-in user if eligible, otherwise spouse, otherwise the logged-in user). - * The Salary Calculator and MPD Goal Calculator checks always run against the logged-in user. - */ -export function useUsStaffGroups(skip?: boolean) { - const { data, loading, error } = useHcmQuery({ - skip, - context: { suppressErrors: true, doNotBatch: true }, - }); - const people = data?.hcm ?? []; - const [user] = people; - - const allAsrIneligible = - !!people.length && - people.every((person) => person?.asrEit?.asrEligibility === false); - const allMhaIneligible = - !!people.length && - people.every((person) => mhaIneligible(person.staffInfo)); - const allPdsGoalCalcIneligible = - !!people.length && - people.every( - (person) => person?.designationSupportCalculatorEligible === false, - ); - - const hasNoStaffAccount = - error?.graphQLErrors.some( - (graphQLError) => graphQLError.extensions?.code === 'NO_STAFF_ACCOUNT', - ) ?? false; - - const hcmPersonNotFound = - error?.graphQLErrors.some( - (graphQLError) => - graphQLError.extensions?.code === 'HCM_PERSON_NOT_FOUND', - ) ?? false; - - const hasNoHcmData = hasNoStaffAccount || hcmPersonNotFound; - - const inAsrIneligibleGroup = hasNoHcmData || allAsrIneligible; - const inSalaryCalcIneligibleGroup = - hasNoHcmData || user?.salaryRequestEligible === false; - const inMhaIneligibleGroup = hasNoHcmData || allMhaIneligible; - const inMpdGoalCalcIneligibleGroup = - hcmPersonNotFound || user?.salaryRequestEligible === false; - const inPdsGoalCalcIneligibleGroup = - hcmPersonNotFound || allPdsGoalCalcIneligible; - - return useMemo( - () => ({ - inAsrIneligibleGroup, - inSalaryCalcIneligibleGroup, - inMhaIneligibleGroup, - inMpdGoalCalcIneligibleGroup, - inPdsGoalCalcIneligibleGroup, - hasNoHcmData, - loading: loading && !data, - }), - [ - inAsrIneligibleGroup, - inSalaryCalcIneligibleGroup, - inMhaIneligibleGroup, - inMpdGoalCalcIneligibleGroup, - inPdsGoalCalcIneligibleGroup, - hasNoHcmData, - loading, - data, - ], - ); -} From 8c40b043f068d05585fdf9529495bb4fd3b7c92b Mon Sep 17 00:00:00 2001 From: Katelyn Grimes Date: Fri, 5 Jun 2026 16:11:46 -0400 Subject: [PATCH 2/2] Add spouse us staff group for ASR and MHA --- .../[calculationId]/index.page.test.tsx | 6 ++- .../salaryCalculator.page.test.tsx | 6 ++- .../LandingTestWrapper.tsx | 2 + .../SalaryCalculatorTestWrapper.tsx | 4 ++ .../StaffSavingFundLayout.test.tsx | 5 ++- .../TopBar/Items/NavMenu/NavMenu.test.tsx | 28 +++---------- .../Shared/UserTypeAccess/UserTypeAccess.tsx | 6 +-- src/components/User/GetUser.graphql | 1 + src/hooks/useIneligibleByGroup.test.tsx | 24 +++++++++++ src/hooks/useIneligibleByGroup.ts | 41 ++++++++++++++----- 10 files changed, 82 insertions(+), 41 deletions(-) diff --git a/pages/accountLists/[accountListId]/hrTools/salaryCalculator/[calculationId]/index.page.test.tsx b/pages/accountLists/[accountListId]/hrTools/salaryCalculator/[calculationId]/index.page.test.tsx index a9615206c6..370a7d989c 100644 --- a/pages/accountLists/[accountListId]/hrTools/salaryCalculator/[calculationId]/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/salaryCalculator/[calculationId]/index.page.test.tsx @@ -1,17 +1,19 @@ import React from 'react'; import { render } from '@testing-library/react'; import { SalaryCalculatorTestWrapper } from 'src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper'; -import { UserTypeEnum } from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import { SalaryCalculatorEditPage } from './index.page'; interface ComponentProps { userType?: UserTypeEnum; + usStaffGroup?: UsStaffGroupEnum; } const Components: React.FC = ({ userType = UserTypeEnum.UsStaff, + usStaffGroup = UsStaffGroupEnum.SeniorStaff, }) => ( - + ); diff --git a/pages/accountLists/[accountListId]/hrTools/salaryCalculator/salaryCalculator.page.test.tsx b/pages/accountLists/[accountListId]/hrTools/salaryCalculator/salaryCalculator.page.test.tsx index 971e1f5ca6..4ef3b3db06 100644 --- a/pages/accountLists/[accountListId]/hrTools/salaryCalculator/salaryCalculator.page.test.tsx +++ b/pages/accountLists/[accountListId]/hrTools/salaryCalculator/salaryCalculator.page.test.tsx @@ -3,17 +3,19 @@ import { render } from '@testing-library/react'; import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers'; import { LandingTestWrapper } from 'src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper'; import { SalaryCalculatorTestWrapper } from 'src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper'; -import { UserTypeEnum } from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import SalaryCalculatorPage, { getServerSideProps } from './index.page'; interface TestComponentProps { userType?: UserTypeEnum; + usStaffGroup?: UsStaffGroupEnum; } const TestComponent: React.FC = ({ userType = UserTypeEnum.UsStaff, + usStaffGroup = UsStaffGroupEnum.SeniorStaff, }) => ( - + ); diff --git a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx index 8c4bf5d868..5d3d2bd285 100644 --- a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx +++ b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx @@ -10,6 +10,7 @@ import { PeopleGroupSupportTypeEnum, SalaryRequestStatusEnum, SecaStatusEnum, + UsStaffGroupEnum, UserPersonTypeEnum, UserTypeEnum, } from 'src/graphql/types.generated'; @@ -134,6 +135,7 @@ export const LandingTestWrapper: React.FC = ({ GetUser: { user: { userType: UserTypeEnum.UsStaff, + usStaffGroup: UsStaffGroupEnum.SeniorStaff, }, }, }} diff --git a/src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper.tsx b/src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper.tsx index 80cebbb6b4..c8e7049c7d 100644 --- a/src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper.tsx +++ b/src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper.tsx @@ -8,6 +8,7 @@ import { StaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccou import { GetUserQuery } from 'src/components/User/GetUser.generated'; import { SalaryRequestStatusEnum, + UsStaffGroupEnum, UserTypeEnum, } from 'src/graphql/types.generated'; import { GoalCalculatorConstantsQuery } from 'src/hooks/goalCalculatorConstants.generated'; @@ -109,6 +110,7 @@ export interface SalaryCalculatorTestWrapperProps { payrollDates?: PayrollDatesQuery['payrollDates']; editing?: boolean; userType?: UserTypeEnum; + usStaffGroup?: UsStaffGroupEnum; } export const SalaryCalculatorTestWrapper: React.FC< @@ -124,6 +126,7 @@ export const SalaryCalculatorTestWrapper: React.FC< payrollDates = [], editing = true, userType = UserTypeEnum.UsStaff, + usStaffGroup = UsStaffGroupEnum.SeniorStaff, }) => { const hcmUserMerged = merge({}, hcmUserMock, hcmUser); const hcmSpouseMerged = merge({}, hcmSpouseMock, hcmSpouse); @@ -157,6 +160,7 @@ export const SalaryCalculatorTestWrapper: React.FC< GetUser: { user: { userType, + usStaffGroup, }, }, PayrollDates: { diff --git a/src/components/HrTools/StaffSavingFund/StaffSavingFundLayout.test.tsx b/src/components/HrTools/StaffSavingFund/StaffSavingFundLayout.test.tsx index d79ac8c23c..7ee9d6c292 100644 --- a/src/components/HrTools/StaffSavingFund/StaffSavingFundLayout.test.tsx +++ b/src/components/HrTools/StaffSavingFund/StaffSavingFundLayout.test.tsx @@ -6,6 +6,7 @@ import { StaffAccountQuery } from 'src/components/Shared/StaffAccount/StaffAccou import { GetUserQuery } from 'src/components/User/GetUser.generated'; import { PeopleGroupSupportTypeEnum, + UsStaffGroupEnum, UserTypeEnum, } from 'src/graphql/types.generated'; import theme from 'src/theme'; @@ -45,10 +46,12 @@ const MockStaffSavingFundProvider = ({ interface ComponentProps { userType?: UserTypeEnum; + usStaffGroup?: UsStaffGroupEnum; } const Components: React.FC = ({ userType = UserTypeEnum.UsStaff, + usStaffGroup = UsStaffGroupEnum.SeniorStaff, }) => ( @@ -59,7 +62,7 @@ const Components: React.FC = ({ }> mocks={{ ...mockStaffAccount, - GetUser: { user: { userType } }, + GetUser: { user: { userType, usStaffGroup } }, Hcm: { hcm: [ { 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 49afcb1116..91596a85ca 100644 --- a/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.test.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.test.tsx @@ -10,12 +10,7 @@ import { render, waitFor } from '__tests__/util/testingLibraryReactMock'; import { LoadCoachingListQuery } from 'src/components/Coaching/LoadCoachingList.generated'; import { HcmQuery } from 'src/components/HrTools/Shared/HcmData/Hcm.generated'; import { GetUserQuery } from 'src/components/User/GetUser.generated'; -import { - AssignmentStatusEnum, - PeopleGroupSupportTypeEnum, - UserPersonTypeEnum, - UserTypeEnum, -} from 'src/graphql/types.generated'; +import { UsStaffGroupEnum, UserTypeEnum } from 'src/graphql/types.generated'; import { UserOptionQuery } from 'src/hooks/UserPreference.generated'; import theme from 'src/theme'; import { GetToolNotificationsQuery } from './GetToolNotifcations.generated'; @@ -132,22 +127,11 @@ describe('NavMenu', () => { , diff --git a/src/components/Shared/UserTypeAccess/UserTypeAccess.tsx b/src/components/Shared/UserTypeAccess/UserTypeAccess.tsx index 7dd9015fac..947bb40d2b 100644 --- a/src/components/Shared/UserTypeAccess/UserTypeAccess.tsx +++ b/src/components/Shared/UserTypeAccess/UserTypeAccess.tsx @@ -67,10 +67,8 @@ export const UserTypeAccess: React.FC = ({ return ; } - if (requireStaffAccount) { - if (hasNoStaffAccount) { - return ; - } + if (requireStaffAccount && hasNoStaffAccount) { + return ; } return children; diff --git a/src/components/User/GetUser.graphql b/src/components/User/GetUser.graphql index e530b6a12b..e04f087b0a 100644 --- a/src/components/User/GetUser.graphql +++ b/src/components/User/GetUser.graphql @@ -13,5 +13,6 @@ query GetUser { primaryDesignation userType usStaffGroup + spouseUsStaffGroup } } diff --git a/src/hooks/useIneligibleByGroup.test.tsx b/src/hooks/useIneligibleByGroup.test.tsx index accf86efca..cdc62f57ad 100644 --- a/src/hooks/useIneligibleByGroup.test.tsx +++ b/src/hooks/useIneligibleByGroup.test.tsx @@ -10,6 +10,7 @@ interface MockUser { usStaffGroup?: UsStaffGroupEnum | null; userType?: UserTypeEnum; staffAccountId?: string | null; + spouseUsStaffGroup?: UsStaffGroupEnum | null; } const renderUseIneligibleByGroup = (user: MockUser = {}) => @@ -75,6 +76,7 @@ describe('useIneligibleByGroup', () => { it('senior staff eligibility', async () => { const { result } = renderUseIneligibleByGroup({ usStaffGroup: UsStaffGroupEnum.SeniorStaff, + spouseUsStaffGroup: null, }); await waitFor(() => expect(result.current.userLoading).toBe(false)); @@ -88,6 +90,7 @@ describe('useIneligibleByGroup', () => { it('new staff eligibility', async () => { const { result } = renderUseIneligibleByGroup({ usStaffGroup: UsStaffGroupEnum.NewStaff, + spouseUsStaffGroup: null, }); await waitFor(() => expect(result.current.userLoading).toBe(false)); @@ -101,6 +104,7 @@ describe('useIneligibleByGroup', () => { it('part time field staff eligibility', async () => { const { result } = renderUseIneligibleByGroup({ usStaffGroup: UsStaffGroupEnum.PartTimeFieldStaff, + spouseUsStaffGroup: null, }); await waitFor(() => expect(result.current.userLoading).toBe(false)); @@ -114,6 +118,7 @@ describe('useIneligibleByGroup', () => { it('paid with designation eligibility', async () => { const { result } = renderUseIneligibleByGroup({ usStaffGroup: UsStaffGroupEnum.PaidWithDesignation, + spouseUsStaffGroup: null, }); await waitFor(() => expect(result.current.userLoading).toBe(false)); @@ -127,6 +132,7 @@ describe('useIneligibleByGroup', () => { it('intern eligibility', async () => { const { result } = renderUseIneligibleByGroup({ usStaffGroup: UsStaffGroupEnum.Intern, + spouseUsStaffGroup: null, }); await waitFor(() => expect(result.current.userLoading).toBe(false)); @@ -140,6 +146,7 @@ describe('useIneligibleByGroup', () => { it('national expat eligibility', async () => { const { result } = renderUseIneligibleByGroup({ usStaffGroup: UsStaffGroupEnum.NationalExpat, + spouseUsStaffGroup: null, }); await waitFor(() => expect(result.current.userLoading).toBe(false)); @@ -150,9 +157,26 @@ describe('useIneligibleByGroup', () => { expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); }); + describe('spouse us staff group', () => { + it('user is null and spouse is senior staff', async () => { + const { result } = renderUseIneligibleByGroup({ + usStaffGroup: null, + spouseUsStaffGroup: UsStaffGroupEnum.SeniorStaff, + }); + + await waitFor(() => expect(result.current.userLoading).toBe(false)); + expect(result.current.inAsrIneligibleGroup).toBe(false); + expect(result.current.inSalaryCalcIneligibleGroup).toBe(true); + expect(result.current.inMhaIneligibleGroup).toBe(false); + expect(result.current.inMpdGoalCalcIneligibleGroup).toBe(true); + expect(result.current.inPdsGoalCalcIneligibleGroup).toBe(true); + }); + }); + it('no us staff group eligibility', async () => { const { result } = renderUseIneligibleByGroup({ usStaffGroup: null, + spouseUsStaffGroup: null, }); await waitFor(() => expect(result.current.userLoading).toBe(false)); diff --git a/src/hooks/useIneligibleByGroup.ts b/src/hooks/useIneligibleByGroup.ts index 22f46a9253..2b16856791 100644 --- a/src/hooks/useIneligibleByGroup.ts +++ b/src/hooks/useIneligibleByGroup.ts @@ -2,25 +2,46 @@ import { useMemo } from 'react'; import { useGetUserQuery } from 'src/components/User/GetUser.generated'; import { UsStaffGroupEnum } from 'src/graphql/types.generated'; +const isInEligibleGroup = ( + group: UsStaffGroupEnum | null | undefined, + eligibleGroups: UsStaffGroupEnum[], +) => { + return typeof group === 'string' && eligibleGroups.includes(group); +}; + export function useIneligibleByGroup() { const { data, loading: userLoading, error: userError } = useGetUserQuery(); const usStaffGroup = data?.user.usStaffGroup; + const spouseUsStaffGroup = data?.user.spouseUsStaffGroup; const userType = data?.user.userType; const hasNoStaffAccount = !data?.user.staffAccountId; - const isSeniorStaff = usStaffGroup === UsStaffGroupEnum.SeniorStaff; - const isNationalExpat = usStaffGroup === UsStaffGroupEnum.NationalExpat; + const { SeniorStaff, NewStaff, NationalExpat, PaidWithDesignation } = + UsStaffGroupEnum; + // Is ASR ineligible if both user and spouse are not Senior Staff, New Staff, or National Expat + const asrGroups = [SeniorStaff, NewStaff, NationalExpat]; const inAsrIneligibleGroup = - !isSeniorStaff && - usStaffGroup !== UsStaffGroupEnum.NewStaff && - !isNationalExpat; - const inSalaryCalcIneligibleGroup = !isSeniorStaff && !isNationalExpat; - const inMhaIneligibleGroup = !isSeniorStaff && !isNationalExpat; - const inMpdGoalCalcIneligibleGroup = !isSeniorStaff; - const inPdsGoalCalcIneligibleGroup = - usStaffGroup !== UsStaffGroupEnum.PaidWithDesignation; + !isInEligibleGroup(usStaffGroup, asrGroups) && + !isInEligibleGroup(spouseUsStaffGroup, asrGroups); + + // Is MHA ineligible if both user and spouse are not Senior Staff or National Expat + const mhaAndSalaryGroups = [SeniorStaff, NationalExpat]; + const inMhaIneligibleGroup = + !isInEligibleGroup(usStaffGroup, mhaAndSalaryGroups) && + !isInEligibleGroup(spouseUsStaffGroup, mhaAndSalaryGroups); + + const inSalaryCalcIneligibleGroup = !isInEligibleGroup( + usStaffGroup, + mhaAndSalaryGroups, + ); + const inMpdGoalCalcIneligibleGroup = !isInEligibleGroup(usStaffGroup, [ + SeniorStaff, + ]); + const inPdsGoalCalcIneligibleGroup = !isInEligibleGroup(usStaffGroup, [ + PaidWithDesignation, + ]); return useMemo( () => ({