Skip to content

fixes income step currency field binding of previously posted values … #245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 24, 2025
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
30 changes: 15 additions & 15 deletions frontend/app/routes/estimator/step-income.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export async function loader({ context, params, request }: Route.LoaderArgs) {
}

export async function action({ context, request }: Route.ActionArgs) {
const { lang } = await getTranslation(request, handle.i18nNamespace);
const formData = await request.formData();
const action = formData.get('action');
const isMarried = context.session.estimator?.maritalStatus === 'married-or-common-law';
Expand All @@ -59,7 +58,7 @@ export async function action({ context, request }: Route.ActionArgs) {
throw i18nRedirect('routes/estimator/step-marital-status.tsx', request);
}
case 'next': {
const result = processIncome(formData, isMarried, lang);
const result = processIncome(formData, isMarried);

if (result.errors) {
return data({ errors: result.errors }, { status: 400 });
Expand All @@ -72,15 +71,15 @@ export async function action({ context, request }: Route.ActionArgs) {
}
}

function processIncome(formData: FormData, isMarried: boolean, lang: Language) {
function processIncome(formData: FormData, isMarried: boolean) {
const positiveDecimal = new RegExp(/^\d*(\.\d\d?)?$/);

const personIncomeSchema = v.pipe(
v.object({
netIncome: v.pipe(
v.string('net-income.error.required'),
v.nonEmpty('net-income.error.required'),
v.transform((input) => removeNumericFormatting(input, lang)),
v.transform((input) => removeNumericFormatting(input)),
v.regex(positiveDecimal, 'net-income.error.invalid'),
v.transform(Number),
v.number('net-income.error.invalid'),
Expand All @@ -89,23 +88,23 @@ function processIncome(formData: FormData, isMarried: boolean, lang: Language) {
workingIncome: v.pipe(
v.string('working-income.error.required'),
v.nonEmpty('working-income.error.required'),
v.transform((input) => removeNumericFormatting(input, lang)),
v.transform((input) => removeNumericFormatting(input)),
v.regex(positiveDecimal, 'working-income.error.invalid'),
v.transform(Number),
v.number('working-income.error.invalid'),
v.minValue(0, 'working-income.error.invalid'),
),
claimedIncome: v.pipe(
v.optional(v.string(), '0'),
v.transform((input) => removeNumericFormatting(input, lang)),
v.transform((input) => removeNumericFormatting(input)),
v.regex(positiveDecimal, 'claimed-income.error.invalid'),
v.transform(Number),
v.number('claimed-income.error.invalid'),
v.minValue(0, 'claimed-income.error.invalid'),
),
claimedRepayment: v.pipe(
v.optional(v.string(), '0'),
v.transform((input) => removeNumericFormatting(input, lang)),
v.transform((input) => removeNumericFormatting(input)),
v.regex(positiveDecimal, 'claimed-repayment.error.invalid'),
v.transform(Number),
v.number('claimed-repayment.error.invalid'),
Expand Down Expand Up @@ -198,7 +197,8 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
</div>
}
defaultValue={
loaderData.formValues?.individualIncome.netIncome ?? previousFormValues.get('income:individual-net-income')
loaderData.formValues?.individualIncome.netIncome ??
removeNumericFormatting(previousFormValues.get('income:individual-net-income'))
}
errorMessage={
errors?.nested?.['individualIncome.netIncome']?.at(0) ? (
Expand Down Expand Up @@ -226,7 +226,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
}
defaultValue={
(loaderData.formValues?.kind === 'married' ? loaderData.formValues.partnerIncome.netIncome : undefined) ??
previousFormValues.get('income:partner-net-income')
removeNumericFormatting(previousFormValues.get('income:partner-net-income'))
}
errorMessage={
errors?.nested?.['partnerIncome.netIncome']?.at(0) ? (
Expand Down Expand Up @@ -264,7 +264,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
}
defaultValue={
loaderData.formValues?.individualIncome.workingIncome ??
previousFormValues.get('income:individual-working-income')
removeNumericFormatting(previousFormValues.get('income:individual-working-income'))
}
errorMessage={
errors?.nested?.['individualIncome.workingIncome']?.at(0) ? (
Expand Down Expand Up @@ -303,7 +303,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
}
defaultValue={
(loaderData.formValues?.kind === 'married' ? loaderData.formValues.partnerIncome.workingIncome : undefined) ??
previousFormValues.get('income:partner-working-income')
removeNumericFormatting(previousFormValues.get('income:partner-working-income'))
}
errorMessage={
errors?.nested?.['partnerIncome.workingIncome']?.at(0) ? (
Expand Down Expand Up @@ -340,7 +340,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
}
defaultValue={
loaderData.formValues?.individualIncome.claimedIncome ??
previousFormValues.get('income:individual-claimed-income')
removeNumericFormatting(previousFormValues.get('income:individual-claimed-income'))
}
errorMessage={
errors?.nested?.['individualIncome.claimedIncome']?.at(0) ? (
Expand Down Expand Up @@ -375,7 +375,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
}
defaultValue={
loaderData.formValues?.individualIncome.claimedRepayment ??
previousFormValues.get('income:individual-claimed-repayment')
removeNumericFormatting(previousFormValues.get('income:individual-claimed-repayment'))
}
errorMessage={
errors?.nested?.['individualIncome.claimedRepayment']?.at(0) ? (
Expand Down Expand Up @@ -411,7 +411,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
}
defaultValue={
(loaderData.formValues?.kind === 'married' ? loaderData.formValues.partnerIncome.claimedIncome : undefined) ??
previousFormValues.get('income:partner-claimed-income')
removeNumericFormatting(previousFormValues.get('income:partner-claimed-income'))
}
errorMessage={
errors?.nested?.['partnerIncome.claimedIncome']?.at(0) ? (
Expand Down Expand Up @@ -449,7 +449,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
defaultValue={
(loaderData.formValues?.kind === 'married'
? loaderData.formValues.partnerIncome.claimedRepayment
: undefined) ?? previousFormValues.get('income:partner-claimed-repayment')
: undefined) ?? removeNumericFormatting(previousFormValues.get('income:partner-claimed-repayment'))
}
errorMessage={
errors?.nested?.['partnerIncome.claimedRepayment']?.at(0) ? (
Expand Down
20 changes: 19 additions & 1 deletion frontend/app/utils/string-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @param input string represenation of a formatted decimal (examples: en:"1,234.56", fr:"1 234,56")
* @param lang Format language (en or fr)
* @returns string representation of the decimal without formatting (examples: "1234.56")
*/
*
export function removeNumericFormatting(input: string, lang: Language): string {
switch (lang) {
case 'en': {
Expand All @@ -16,6 +16,24 @@ export function removeNumericFormatting(input: string, lang: Language): string {
}
}
}
*/
export function removeNumericFormatting(input: string | undefined): string {
if (input === undefined) return '';

const isFrench = input.includes(' ') || input.lastIndexOf(',') >= input.length - 3;
const isEnglish = input.indexOf(',') < input.length - 2 || input.lastIndexOf('.') >= input.length - 3;

if (isFrench) {
const output = input.replaceAll(' ', '').replaceAll(',', '.');
return output;
}
if (isEnglish) {
const output = input.replaceAll(',', '');
return output;
}

return input;
}

/**
* Generate a random string using the provided characters, or alphanumeric characters if none are provided.
Expand Down
37 changes: 26 additions & 11 deletions frontend/tests/utils/string-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,33 @@ describe('string-utils', () => {
});

it.each([
{ input: '.00', output: '.00', lang: 'en' },
{ input: '0.00', output: '0.00', lang: 'en' },
{ input: '1,234.56', output: '1234.56', lang: 'en' },
{ input: '123.56', output: '123.56', lang: 'en' },
{ input: ',00', output: '.00', lang: 'fr' },
{ input: '0,00', output: '0.00', lang: 'fr' },
{ input: '1 234,56', output: '1234.56', lang: 'fr' },
{ input: '123,56', output: '123.56', lang: 'fr' },
{ input: '.00', output: '.00' },
{ input: '0.00', output: '0.00' },
{ input: '1,234.56', output: '1234.56' },
{ input: '123.56', output: '123.56' },
{ input: ',00', output: '.00' },
{ input: '0,00', output: '0.00' },
{ input: '1 234,56', output: '1234.56' },
{ input: '123,56', output: '123.56' },
{ input: '1 234', output: '1234' },
{ input: '1,234', output: '1234' },
{ input: '1,234,567', output: '1234567' },
{ input: '1 234 567', output: '1234567' },
{ input: '1', output: '1' },
{ input: '12', output: '12' },
{ input: '123', output: '123' },
{ input: '1.1', output: '1.1' },
{ input: '1,1', output: '1.1' },
{ input: '1,12', output: '1.12' },
{ input: '1.12', output: '1.12' },
{ input: '123.12', output: '123.12' },
{ input: '123,12', output: '123.12' },
{ input: '1,234.12', output: '1234.12' },
{ input: '1 234,12', output: '1234.12' },
])(
'removeNumericFormatting should remove formatting from a formatted decimal string representation',
({ input, lang, output }) => {
const str = removeNumericFormatting(input, lang as Language);
'removeNumericFormatting should remove formatting from a formatted decimal string representation ($input)',
({ input, output }) => {
const str = removeNumericFormatting(input);
expect(str).toBe(output);
},
);
Expand Down