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
Expand Up @@ -435,6 +435,26 @@ describe('useAdditionalSalaryRequestForm', () => {
expect(errors.childrenCollegeEducation).toContain('$21,000.00');
});

it('should accept empty string for currency fields', async () => {
const { result } = renderHook(
() =>
useAdditionalSalaryRequestForm({
...defaultFormValues,
adoption: '',
phoneNumber: '555-123-4567',
emailAddress: 'test@example.com',
}),
{ wrapper: TestWrapper },
);

let errors: Record<string, string> = {};
await act(async () => {
errors = await result.current.validateForm();
});

expect(errors.adoption).toBeUndefined();
});

it('should validate additional info when exceedsCap is true', async () => {
const { result } = renderHook(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,105 +95,107 @@

const createCurrencyValidation = useCallback(
(fieldName: string, max?: number) => {
let schema = amount(fieldName, t);
let schema = amount(fieldName, t).nullable();
if (max !== null && max !== undefined) {
schema = schema.max(
max,
t('Exceeds {{amount}} limit', {
amount: currencyFormat(max, 'USD', locale, {
showTrailingZeros: true,
}),
}),
);
}
return schema;
},
[t, locale],
);

const defaultInitialValues: CompleteFormValues = {
...Object.fromEntries(fieldConfig.map(({ key }) => [key, '0'])),
totalAdditionalSalaryRequested: '0',
additionalInfo: '',
deductTaxDeferredPercent: false,
deductRothPercent: false,
phoneNumber: user?.staffInfo?.primaryPhoneNumber || '',
emailAddress: user?.staffInfo?.emailAddress || '',
} as CompleteFormValues;

const initialValues: CompleteFormValues = useMemo(() => {
if (providedInitialValues) {
return providedInitialValues;
}
const initialValuesRef = useRef<CompleteFormValues | null>(null);

const request = requestData?.latestAdditionalSalaryRequest;
if (!request) {
return defaultInitialValues;
if (!initialValuesRef.current) {
if (providedInitialValues) {
initialValuesRef.current = providedInitialValues;
} else {
const request = requestData?.latestAdditionalSalaryRequest;
if (request) {
initialValuesRef.current = {
...Object.fromEntries(
fieldConfig.map(({ key }) => [
key,
String((request[key as keyof typeof request] as number) ?? ''),
]),
),
deductTaxDeferredPercent: request.deductTaxDeferredPercent || false,
deductRothPercent: request.deductRothPercent || false,
phoneNumber:
request.phoneNumber || user?.staffInfo?.primaryPhoneNumber || '',
emailAddress:
request.emailAddress || user?.staffInfo?.emailAddress || '',
totalAdditionalSalaryRequested:
request.totalAdditionalSalaryRequested || '',
additionalInfo: request.additionalInfo || '',
} as CompleteFormValues;
}
}
}

return {
...Object.fromEntries(
fieldConfig.map(({ key }) => [
key,
String((request[key as keyof typeof request] as number) ?? ''),
]),
),
deductTaxDeferredPercent: request.deductTaxDeferredPercent || false,
deductRothPercent: request.deductRothPercent || false,
phoneNumber:
request.phoneNumber || user?.staffInfo?.primaryPhoneNumber || '',
emailAddress: request.emailAddress || user?.staffInfo?.emailAddress || '',
totalAdditionalSalaryRequested:
request.totalAdditionalSalaryRequested || '',
additionalInfo: request.additionalInfo || '',
} as CompleteFormValues;
}, [providedInitialValues, requestData?.latestAdditionalSalaryRequest, user]);
const initialValues = initialValuesRef.current ?? defaultInitialValues;

const getMaxForField = useCallback(
(field: (typeof fieldConfig)[number]): number | undefined => {
if (!field.salaryInfoIntKey || !field.salaryInfoUssKey || !salaryInfo) {
return undefined;
}
const key = isInternational
? field.salaryInfoIntKey
: field.salaryInfoUssKey;
return salaryInfo[key] as number | undefined;
},
[salaryInfo, isInternational],
);

const validationSchema = useMemo(
() =>
yup.object({
...Object.fromEntries(
fieldConfig.map((field) => [
field.key,
createCurrencyValidation(t(field.label), getMaxForField(field)),
]),
),
deductTaxDeferredPercent: yup.boolean(),
deductRothPercent: yup.boolean(),
phoneNumber: phoneNumber(i18n.t).required(
i18n.t('Phone Number is required.'),
),
emailAddress: yup
.string()
.required(t('Email address is required'))
.email(t('Please enter a valid email address')),
totalAdditionalSalaryRequested: yup
.number()
.test(
'total-within-remaining-allowable-salary',
t('Exceeds account balance.'),
function () {
const total = getTotal(this.parent as CompleteFormValues);

if (total > 0) {
if (total >= 0) {
lastValidTotalRef.current = total;
}
const stableTotal = total > 0 ? total : lastValidTotalRef.current;

return stableTotal <= primaryAccountBalance;
return lastValidTotalRef.current <= primaryAccountBalance;

Check notice on line 198 in src/components/Reports/AdditionalSalaryRequest/Shared/useAdditionalSalaryRequestForm.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ Getting better: Complex Method

useAdditionalSalaryRequestForm decreases in cyclomatic complexity from 43 to 42, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check warning on line 198 in src/components/Reports/AdditionalSalaryRequest/Shared/useAdditionalSalaryRequestForm.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Bumpy Road Ahead

useAdditionalSalaryRequestForm has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.
},
),
additionalInfo: yup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,58 @@

export const ValidationAlert: React.FC = () => {
const { t } = useTranslation();
const { submitCount, isValid, values, errors } =
const { submitCount, values, errors } =
useFormikContext<CompleteFormValues>();
const { salaryInfo, isInternational } = useAdditionalSalaryRequest();

const exceedingLimitFields = useMemo(() => {
if (!submitCount || !salaryInfo) {
return [];
}

return fieldConfig
.filter(({ key, salaryInfoIntKey, salaryInfoUssKey }) => {
if (!salaryInfoIntKey || !salaryInfoUssKey) {
return false;
}
const maxKey = isInternational ? salaryInfoIntKey : salaryInfoUssKey;
const max = salaryInfo[maxKey] as number | undefined;
if (!max) {
return false;
}
const value = parseFloat(
values[key as keyof CompleteFormValues] as string,
);
return !isNaN(value) && value > max;
})
.map(({ label }) => t(label));
}, [submitCount, salaryInfo, isInternational, values, t]);

if (!submitCount || isValid) {
const hasRequiredErrors = Object.keys(errors).some((key) =>
['phoneNumber', 'emailAddress', 'additionalInfo'].includes(key),
);

const hasErrors =
hasRequiredErrors ||
exceedingLimitFields.length > 0 ||
!!errors.totalAdditionalSalaryRequested;

if (!submitCount || !hasErrors) {
return null;
}

const hasFieldErrors = Object.keys(errors).some(
(key) => key !== 'totalAdditionalSalaryRequested',
);

return (
<Alert severity="error" sx={{ mt: 2, '& ul': { m: 0, pl: 3 } }}>
{t('Your form is missing information.')}
<ul>
{hasFieldErrors && (
{hasRequiredErrors && (
<li>{t('Please enter a value for all required fields.')}</li>
)}
{exceedingLimitFields.length > 0 && (
<li>
{t('The following fields exceed their limits: {{fields}}', {
fields: exceedingLimitFields.join(', '),
interpolation: { escapeValue: false },

Check warning on line 62 in src/components/Reports/AdditionalSalaryRequest/SharedComponents/ValidationAlert.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Complex Method

ValidationAlert:React.FC increases in cyclomatic complexity from 13 to 15, threshold = 10. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
})}
</li>
)}
Expand Down
Loading