Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,6 @@
"Counseling": "Counseling",
"Counseling that is not for the treatment of a medical condition": "Counseling that is not for the treatment of a medical condition",
"Country": "Country",
"Couple in hotel/dorm room": "Couple in hotel/dorm room",
"Couples": "Couples",
"Create": "Create",
"Create a new contact for \"{{ name }}\"": "Create a new contact for \"{{ name }}\"",
Expand Down Expand Up @@ -1297,7 +1296,7 @@
"Fair Rental Value": "Fair Rental Value",
"Falkland Islands (Malvinas)": "Falkland Islands (Malvinas)",
"false": "false",
"Family in a hotel/room": "Family in a hotel/room",
"Family in hotel/suite dorm room": "Family in hotel/suite dorm room",
"Family Size": "Family Size",
"Family Size must be one of the options": "Family Size must be one of the options",
"Family Status": "Family Status",
Expand Down Expand Up @@ -1758,7 +1757,6 @@
"Loading donations graph": "Loading donations graph",
"Loading MPD goals": "Loading MPD goals",
"Loading...": "Loading...",
"Local / Commuting": "Local / Commuting",
"Locale": "Locale",
"Location": "Location",
"Log": "Log",
Expand Down Expand Up @@ -1805,6 +1803,7 @@
"Mark all as read": "Mark all as read",
"Marked Do Not Ask": "Marked Do Not Ask",
"Married": "Married",
"Married couple in hotel/suite dorm room": "Married couple in hotel/suite dorm room",
"Marshall Islands": "Marshall Islands",
"Martinique": "Martinique",
"Mauritania": "Mauritania",
Expand Down Expand Up @@ -2701,7 +2700,7 @@
"Share Account": "Share Account",
"Share this ministry account with other team members": "Share this ministry account with other team members",
"Share this organization with other team members": "Share this organization with other team members",
"Sharing 2 in hotel/dorm room": "Sharing 2 in hotel/dorm room",
"Sharing 2 in hotel/suite dorm room": "Sharing 2 in hotel/suite dorm room",
"Short Term Assignment": "Short Term Assignment",
"Show {{amount}} More": "Show {{amount}} More",
"Show {{partnerCount}} Partners": "Show {{partnerCount}} Partners",
Expand Down Expand Up @@ -3282,6 +3281,7 @@
"View/Edit {{name}}": "View/Edit {{name}}",
"Virgin Islands, British": "Virgin Islands, British",
"Virgin Islands, U.S.": "Virgin Islands, U.S.",
"Virtual / Local / Commuting": "Virtual / Local / Commuting",
"Voluntary 403b Retirement Plan": "Voluntary 403b Retirement Plan",
"Wallis and Futuna": "Wallis and Futuna",
"Want your monthly transfer to end at a certain point? You can set an end date—super handy! Just a heads-up: once it’s there, it can’t be removed, but you can change it to a different date if needed.": "Want your monthly transfer to end at a certain point? You can set an end date—super handy! Just a heads-up: once it’s there, it can’t be removed, but you can change it to a different date if needed.",
Expand Down Expand Up @@ -3394,11 +3394,13 @@
"You have a Board approved Maximum Allowable Salary (CAP) and your Additional Salary Request exceeds that amount. As a result we need to get their approval for this request. We'll forward your request to them and get back to you with their decision.": "You have a Board approved Maximum Allowable Salary (CAP) and your Additional Salary Request exceeds that amount. As a result we need to get their approval for this request. We'll forward your request to them and get back to you with their decision.",
"You have a Board approved Maximum Allowable Salary (CAP) and your salary request exceeds that amount. As a result we need to get their approval for this request. We'll forward your request to them and get back to you with their decision.": "You have a Board approved Maximum Allowable Salary (CAP) and your salary request exceeds that amount. As a result we need to get their approval for this request. We'll forward your request to them and get back to you with their decision.",
"You have a previously approved Maximum Allowable Salary (CAP). Any adjustment that may exceed this cap must be submitted for further approval.": "You have a previously approved Maximum Allowable Salary (CAP). Any adjustment that may exceed this cap must be submitted for further approval.",
"You have a roommate, or your own bedroom in a shared suite.": "You have a roommate, or your own bedroom in a shared suite.",
"You have a saved Additional Salary Request in progress that has not been submitted.": "You have a saved Additional Salary Request in progress that has not been submitted.",
"You have no designation accounts": "You have no designation accounts",
"You have no expected donations this month": "You have no expected donations this month",
"You have no Google Contact groups/labels": "You have no Google Contact groups/labels",
"You have no responsibility centers.": "You have no responsibility centers.",
"You have no roommate and no suitemates.": "You have no roommate and no suitemates.",
"You have reached the end of the goal calculator.": "You have reached the end of the goal calculator.",
"You have received no donations in the last fourteen months": "You have received no donations in the last fourteen months",
"You haven't connected a Google account yet": "You haven't connected a Google account yet",
Expand All @@ -3419,7 +3421,9 @@
"You'll also need to search by contact name, email address, phone number, or partner number.": "You'll also need to search by contact name, email address, phone number, or partner number.",
"You're all done": "You're all done",
"You're all set!": "You're all set!",
"You're attending virtually or commuting daily and not using NSO housing.": "You're attending virtually or commuting daily and not using NSO housing.",
"You're currently being logged out.": "You're currently being logged out.",
"You're staying together as a couple or family.": "You're staying together as a couple or family.",
"You've successfully submitted your {{formTitle}}!": "You've successfully submitted your {{formTitle}}!",
"You've successfully submitted your Salary Calculation Form!": "You've successfully submitted your Salary Calculation Form!",
"You've successfully updated your {{formTitle}}!": "You've successfully updated your {{formTitle}}!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,57 @@ describe('NsoDetails', () => {
getByRole('radio', { name: 'Single in hotel/dorm room' }),
).toBeInTheDocument();
expect(
getByRole('radio', { name: 'Sharing 2 in hotel/dorm room' }),
getByRole('radio', { name: 'Sharing 2 in hotel/suite dorm room' }),
).toBeInTheDocument();
expect(
getByRole('radio', { name: 'Couple in hotel/dorm room' }),
getByRole('radio', { name: 'Married couple in hotel/suite dorm room' }),
).toBeInTheDocument();
expect(
getByRole('radio', { name: 'Family in a hotel/room' }),
getByRole('radio', { name: 'Family in hotel/suite dorm room' }),
).toBeInTheDocument();
expect(
getByRole('radio', { name: 'Local / Commuting' }),
getByRole('radio', { name: 'Virtual / Local / Commuting' }),
).toBeInTheDocument();
});

it.each([
['Single in hotel/dorm room', 'You have no roommate and no suitemates.'],
[
'Sharing 2 in hotel/suite dorm room',
'You have a roommate, or your own bedroom in a shared suite.',
],
[
'Married couple in hotel/suite dorm room',
"You're staying together as a couple or family.",
],
[
'Family in hotel/suite dorm room',
"You're staying together as a couple or family.",
],
[
'Virtual / Local / Commuting',
"You're attending virtually or commuting daily and not using NSO housing.",
],
])(
'explains the "%s" housing option under its label',
(label, description) => {
const { getByRole } = render(<TestComponent />);

const descriptionId = getByRole('radio', { name: label }).getAttribute(
'aria-describedby',
);
expect(document.getElementById(descriptionId ?? '')).toHaveTextContent(
description,
);
},
);

it('saves the housing enum constant', async () => {
const { getByRole } = render(<TestComponent onCall={mutationSpy} />);

userEvent.click(getByRole('radio', { name: 'Family in a hotel/room' }));
userEvent.click(
getByRole('radio', { name: 'Family in hotel/suite dorm room' }),
);

await waitFor(() =>
expect(mutationSpy).toHaveGraphqlOperation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { Stack } from '@mui/material';
import { TFunction, useTranslation } from 'react-i18next';
import * as yup from 'yup';
import { CurrencyAdornment } from 'src/components/HrTools/Shared/Adornments';
import {
NewStaffQuestionnaireNsoHousingEnum,
NewStaffQuestionnaireNsoSessionsEnum,
} from 'src/graphql/types.generated';
import {
getLocalizedNsoHousing,
getLocalizedNsoHousingDescription,
} from 'src/lib/functions/getLocalizedNsoHousing';
import { getLocalizedNsoSessions } from 'src/lib/functions/getLocalizedNsoSessions';
import { NumberQuestion } from '../Shared/NumberQuestion';
import { RadioOption, RadioQuestion } from '../Shared/RadioQuestion';
import { getAmountSchema } from '../Shared/helpers/getAmountSchema';
Expand All @@ -24,18 +33,30 @@ export const NsoDetails: React.FC = () => {

const schema = useMemo(() => getNsoDetailsSchema(t), [t]);

const housingOptions: RadioOption[] = [
{ value: 'SINGLE_ROOM', label: t('Single in hotel/dorm room') },
{ value: 'SHARED_ROOM', label: t('Sharing 2 in hotel/dorm room') },
{ value: 'COUPLE_ROOM', label: t('Couple in hotel/dorm room') },
{ value: 'FAMILY_ROOM', label: t('Family in a hotel/room') },
{ value: 'LOCAL_COMMUTING', label: t('Local / Commuting') },
];
const housingOptions = useMemo<RadioOption[]>(
Comment thread
wjames111 marked this conversation as resolved.
() =>
[
NewStaffQuestionnaireNsoHousingEnum.SingleRoom,
NewStaffQuestionnaireNsoHousingEnum.SharedRoom,
NewStaffQuestionnaireNsoHousingEnum.CoupleRoom,
NewStaffQuestionnaireNsoHousingEnum.FamilyRoom,
NewStaffQuestionnaireNsoHousingEnum.LocalCommuting,
].map((value) => ({
value,
label: getLocalizedNsoHousing(t, value),
description: getLocalizedNsoHousingDescription(t, value),
})),
[t],
);

const sessionOptions: RadioOption[] = [
{ value: 'IBS_AND_NSO', label: t('IBS and NSO') },
{ value: 'NSO', label: t('NSO') },
];
const sessionOptions = useMemo<RadioOption[]>(
() =>
[
NewStaffQuestionnaireNsoSessionsEnum.IbsAndNso,
NewStaffQuestionnaireNsoSessionsEnum.Nso,
].map((value) => ({ value, label: getLocalizedNsoSessions(t, value) })),
[t],
);

return (
<Stack spacing={4}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,31 @@ describe('RadioQuestion', () => {
);
});

it('shows an option description under its label and links it to the radio', () => {
const { getByRole, getByText } = render(
<NsoMpdQuestionnaireTestWrapper>
<RadioQuestion
fieldName="geographicLocation"
schema={schema}
label="Pick one"
options={[
{ value: 'A', label: 'Option A', description: 'The first choice' },
{ value: 'B', label: 'Option B' },
]}
/>
</NsoMpdQuestionnaireTestWrapper>,
);

const radio = getByRole('radio', { name: 'Option A' });
expect(getByText('The first choice')).toHaveAttribute(
'id',
radio.getAttribute('aria-describedby'),
);
expect(getByRole('radio', { name: 'Option B' })).not.toHaveAttribute(
'aria-describedby',
);
});

it('lays options out in a row when row is set', () => {
const { getByRole } = render(<TestComponent row />);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { FormControlLabel, Radio, RadioGroup } from '@mui/material';
import { RadioGroup } from '@mui/material';
import * as yup from 'yup';
import { DescribedRadioOption } from 'src/components/HrTools/Shared/DescribedRadioOption';
import { LabeledField } from './LabeledField';
import {
QuestionnaireField,
Expand All @@ -10,6 +11,8 @@ import {
export interface RadioOption {
value: string;
label: string;
/** Shown under the label in muted text to clarify what the option means. */
description?: string;
}

interface RadioQuestionProps {
Expand Down Expand Up @@ -38,23 +41,20 @@ export const RadioQuestion: React.FC<RadioQuestionProps> = ({
saveOnChange: true,
});

const hasDescriptions = options.some((option) => option.description);

return (
<LabeledField label={label} required error={error} helperText={helperText}>
{(aria) => (
<RadioGroup
row={row}
sx={{ paddingInline: 2 }}
sx={{ paddingInline: 2, gap: hasDescriptions ? 2 : 0 }}
aria-required
{...aria}
{...fieldProps}
>
{options.map((option) => (
<FormControlLabel
key={option.value}
value={option.value}
control={<Radio />}
label={option.label}
/>
<DescribedRadioOption key={option.value} {...option} />
))}
</RadioGroup>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,16 @@
DialogContent,
DialogTitle,
FormControl,
FormControlLabel,
FormLabel,
Radio,
RadioGroup,
SxProps,
Theme,
Typography,
} from '@mui/material';
import { visuallyHidden } from '@mui/utils';
import { Formik, FormikProps } from 'formik';
import { useTranslation } from 'react-i18next';
import * as yup from 'yup';
import { DescribedRadioOption } from 'src/components/HrTools/Shared/DescribedRadioOption';
import { DesignationSupportFormType } from 'src/graphql/types.generated';

export interface CreateGoalDialogProps {
Expand Down Expand Up @@ -110,48 +108,18 @@
name="formType"
value={values.formType}
onChange={handleChange}
sx={{ gap: 2 }}
>
{formTypeOptions.map(
({ value, title, description }, index) => (
<FormControlLabel
key={value}
value={value}
control={
<Radio
inputProps={{
'aria-labelledby': `${value}-title`,
'aria-describedby': `${value}-desc`,
}}
/>
}
label={
<>
<Typography
id={`${value}-title`}
variant="subtitle1"
component="span"
display="block"
>
{title}
</Typography>
<Typography
id={`${value}-desc`}
variant="body2"
color="text.secondary"
component="span"
display="block"
>
{description}
</Typography>
</>
}
sx={{
alignItems: 'flex-start',
mb: index < formTypeOptions.length - 1 ? 2 : 0,
}}
/>
),
)}
{formTypeOptions.map(({ value, title, description }) => (
<DescribedRadioOption
key={value}
value={value}
label={title}
description={description}
labelVariant="subtitle1"
descriptionVariant="body2"
/>
))}

Check notice on line 122 in src/components/HrTools/PdsGoalCalculator/GoalsList/CreateGoalDialog.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ No longer an issue: Large Method

CreateGoalDialog:React.FC<CreateGoalDialogProps> is no longer above the threshold for lines of code
</RadioGroup>
</FormControl>
</DialogContent>
Expand Down
36 changes: 36 additions & 0 deletions src/components/HrTools/Shared/DescribedRadioOption.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { RadioGroup } from '@mui/material';
import { render } from '@testing-library/react';
import { DescribedRadioOption } from './DescribedRadioOption';

describe('DescribedRadioOption', () => {
it('names the radio by its label alone and links the description via aria-describedby', () => {
const { getByRole, getByText } = render(
<RadioGroup>
<DescribedRadioOption
value="A"
label="Option A"
description="The first choice"
/>
</RadioGroup>,
);

const radio = getByRole('radio', { name: 'Option A' });
expect(getByText('The first choice')).toHaveAttribute(
'id',
radio.getAttribute('aria-describedby'),
);
});

it('omits aria-describedby when there is no description', () => {
const { getByRole } = render(
<RadioGroup>
<DescribedRadioOption value="B" label="Option B" />
</RadioGroup>,
);

expect(getByRole('radio', { name: 'Option B' })).not.toHaveAttribute(
'aria-describedby',
);
});
});
Loading
Loading