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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';

Check warning on line 1 in src/components/HrTools/PdsGoalCalculator/PdsGoalCalculator.test.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Code Duplication

The module contains 2 functions with similar structure: 're-enables Continue when switching from Full-time to Part-time hides the only invalid field','re-enables Continue when switching from Hourly to Salaried hides the only invalid field'. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
Expand Down Expand Up @@ -97,6 +97,53 @@
await waitFor(() => expect(continueButton).not.toBeDisabled());
});

it('hides the Back button on the first step', async () => {
const { findByRole, queryByRole } = render(
<PdsGoalCalculatorTestWrapper>
<PdsGoalCalculator />
</PdsGoalCalculatorTestWrapper>,
);

await findByRole('button', { name: /continue/i });
expect(queryByRole('button', { name: /back/i })).not.toBeInTheDocument();
});

it('shows the Back button after advancing past the first step', async () => {
const { findByRole } = render(
<PdsGoalCalculatorTestWrapper>
<PdsGoalCalculator />
</PdsGoalCalculatorTestWrapper>,
);

const continueButton = await findByRole('button', { name: /continue/i });
await waitFor(() => expect(continueButton).not.toBeDisabled());
userEvent.click(continueButton);

expect(await findByRole('button', { name: /back/i })).toBeInTheDocument();
});

it('clicking Back returns to the previous step', async () => {
const { findByRole, queryByRole } = render(
<PdsGoalCalculatorTestWrapper>
<PdsGoalCalculator />
</PdsGoalCalculatorTestWrapper>,
);

const continueButton = await findByRole('button', { name: /continue/i });
await waitFor(() => expect(continueButton).not.toBeDisabled());
userEvent.click(continueButton);

const backButton = await findByRole('button', { name: /back/i });
userEvent.click(backButton);

await waitFor(() =>
expect(queryByRole('button', { name: /back/i })).not.toBeInTheDocument(),
);
expect(
await findByRole('heading', { level: 6, name: 'Calculator Setup' }),
).toBeInTheDocument();
});

it('re-enables Continue when switching from Full-time to Part-time hides the only invalid field', async () => {
const { findByRole, getByRole, queryByRole } = render(
<PdsGoalCalculatorTestWrapper
Expand Down
17 changes: 9 additions & 8 deletions src/components/HrTools/PdsGoalCalculator/PdsGoalCalculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,20 @@ const MainContent: React.FC = () => {
usePdsGoalCalculator();
const { allValid } = useAutosaveForm();

const isFirstStep = stepIndex === 0;
const isLastStep = stepIndex === steps.length - 1;

return (
<>
<CurrentStep />
{!isLastStep && (
<DirectionButtons
formTitle={currentStep.title}
handleNextStep={handleContinue}
handlePreviousStep={handlePreviousStep}
disableNext={!allValid}
/>
)}
<DirectionButtons
formTitle={currentStep.title}
handleNextStep={handleContinue}
handlePreviousStep={handlePreviousStep}
showBackButton={!isFirstStep}
hideNextButton={isLastStep}
disableNext={!allValid}
/>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface TestComponentProps {
buttonTitle?: string;
isEdit?: boolean;
disableNext?: boolean;
noDiscard?: boolean;
}

const TestComponent: React.FC<TestComponentProps> = ({
Expand All @@ -35,6 +36,7 @@ const TestComponent: React.FC<TestComponentProps> = ({
buttonTitle,
isEdit,
disableNext,
noDiscard = false,
}) => (
<ThemeProvider theme={theme}>
<TestRouter
Expand All @@ -51,7 +53,7 @@ const TestComponent: React.FC<TestComponentProps> = ({
isSubmission={isSubmission}
handleNextStep={handleNextStep}
handlePreviousStep={handlePreviousStep}
handleDiscard={handleDiscard}
handleDiscard={noDiscard ? undefined : handleDiscard}
overrideNext={overrideNext}
showBackButton={showBackButton}
buttonTitle={buttonTitle}
Expand Down Expand Up @@ -155,6 +157,53 @@ describe('DirectionButtons', () => {
});
});

it('renders Discard, Back, and Continue in left-to-right order', async () => {
const { findAllByRole } = render(<TestComponent showBackButton={true} />);

const buttons = await findAllByRole('button');
expect(buttons).toHaveLength(3);
expect(buttons[0]).toHaveTextContent(/discard/i);
expect(buttons[1]).toHaveTextContent(/back/i);
expect(buttons[2]).toHaveTextContent(/continue/i);
});

it('renders Discard, Back, and Submit in left-to-right order during submission', async () => {
const { findAllByRole } = render(
<TestComponent showBackButton={true} isSubmission={true} />,
);

const buttons = await findAllByRole('button');
expect(buttons).toHaveLength(3);
expect(buttons[0]).toHaveTextContent(/discard/i);
expect(buttons[1]).toHaveTextContent(/back/i);
expect(buttons[2]).toHaveTextContent(/submit/i);
});

it('renders Back to the left of Continue when there is no Discard handler', async () => {
const { findAllByRole } = render(
<TestComponent noDiscard={true} showBackButton={true} />,
);

const buttons = await findAllByRole('button');
expect(buttons).toHaveLength(2);
expect(buttons[0]).toHaveTextContent(/back/i);
expect(buttons[1]).toHaveTextContent(/continue/i);
});

it('renders only Continue (right-aligned) when there is no Discard, no Back, and not a submission', async () => {
const { findAllByRole } = render(<TestComponent noDiscard={true} />);

const buttons = await findAllByRole('button');
expect(buttons).toHaveLength(1);
expect(buttons[0]).toHaveTextContent(/continue/i);

// The Continue button's wrapper Box uses `ml: 'auto'` to push it to the
// right edge when no left-side controls are rendered. Walk up from the
// button to confirm that wrapper is the only child of the outer flex row.
const outerRow = buttons[0].closest('.MuiBox-root')?.parentElement;
expect(outerRow?.children).toHaveLength(1);
});

it('renders Discard Changes button', async () => {
const { findByRole, getByRole } = render(<TestComponent isEdit={true} />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface DirectionButtonsProps {
actionRequired?: boolean;
overrideNext?: () => void;
showBackButton?: boolean;
hideNextButton?: boolean;
isEdit?: boolean;
additionalApproval?: boolean;
splitAsr?: boolean;
Expand All @@ -43,6 +44,7 @@ export const DirectionButtons: React.FC<DirectionButtonsProps> = ({
isSubmission,
overrideNext,
showBackButton,
hideNextButton,
submitForm,
validateForm,
submitCount,
Expand Down Expand Up @@ -90,73 +92,73 @@ export const DirectionButtons: React.FC<DirectionButtonsProps> = ({
setOpenDiscardModal(false);
};

return (
<Box
const backButton = showBackButton && (
<Button
variant="contained"
startIcon={<ChevronLeft />}
sx={{
mt: 5,
display: 'flex',
justifyContent:
!handleDiscard && !showBackButton && !isSubmission
? 'flex-end'
: 'space-between',
bgcolor: 'grey.300',
color: 'text.primary',
'&:hover': {
bgcolor: 'grey.400',
},
fontWeight: 'bold',
}}
onClick={handlePreviousStep}
>
{handleDiscard && (
<Button
sx={{ color: 'error.light', px: 2, py: 1, fontWeight: 'bold' }}
onClick={() => setOpenDiscardModal(true)}
>
{isEdit ? t('Discard Changes') : t('Discard')}
</Button>
{t('Back')}
</Button>
);

return (
<Box sx={{ mt: 5, display: 'flex', justifyContent: 'space-between' }}>
{(handleDiscard || showBackButton) && (
<Box sx={{ display: 'flex', gap: 2 }}>
{handleDiscard ? (
<Button
sx={{ color: 'error.light', px: 2, py: 1, fontWeight: 'bold' }}
onClick={() => setOpenDiscardModal(true)}
>
{isEdit ? t('Discard Changes') : t('Discard')}
</Button>
) : (
backButton
)}
</Box>
)}

<Box sx={{ display: 'flex', gap: 2 }}>
{showBackButton && (
<Button
variant="contained"
sx={{
bgcolor: 'grey.300',
color: 'text.primary',
'&:hover': {
bgcolor: 'grey.400',
},
fontWeight: 'bold',
}}
onClick={handlePreviousStep}
>
<ChevronLeft sx={{ mr: 1 }} />
{t('Back')}
</Button>
)}
{isSubmission ? (
<Button
variant="contained"
color="primary"
onClick={handleSubmit}
disabled={submitCount ? !isValid : false}
>
{t('Submit')}
<ChevronRight sx={{ ml: 1 }} />
</Button>
) : (
<Tooltip
title={
disableNext ? t('Complete all required fields to continue') : ''
}
>
<Box component="span">
<Button
variant="contained"
color="primary"
onClick={overrideNext ?? handleNextStep}
disabled={disableNext}
>
{buttonTitle ?? t('Continue')}
<ChevronRight sx={{ ml: 1 }} />
</Button>
</Box>
</Tooltip>
)}
<Box sx={{ display: 'flex', gap: 2, ml: 'auto' }}>
{handleDiscard && backButton}
{!hideNextButton &&
(isSubmission ? (
<Button
variant="contained"
color="primary"
endIcon={<ChevronRight />}
onClick={handleSubmit}
disabled={submitCount ? !isValid : false}
>
{t('Submit')}
</Button>
) : (
<Tooltip
title={
disableNext ? t('Complete all required fields to continue') : ''
}
>
<Box component="span">
<Button
variant="contained"
color="primary"
endIcon={<ChevronRight />}
onClick={overrideNext ?? handleNextStep}
disabled={disableNext}
>
{buttonTitle ?? t('Continue')}
</Button>
</Box>
</Tooltip>
))}
</Box>
{openSubmitModal && (
<SubmitModal
Expand Down
Loading