Skip to content

Commit c1b3412

Browse files
fixes income step currency field binding of previously posted values across language toggles (#245)
1 parent 1ce09e8 commit c1b3412

File tree

3 files changed

+60
-27
lines changed

3 files changed

+60
-27
lines changed

frontend/app/routes/estimator/step-income.tsx

+15-15
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export async function loader({ context, params, request }: Route.LoaderArgs) {
4949
}
5050

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

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

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

7877
const personIncomeSchema = v.pipe(
7978
v.object({
8079
netIncome: v.pipe(
8180
v.string('net-income.error.required'),
8281
v.nonEmpty('net-income.error.required'),
83-
v.transform((input) => removeNumericFormatting(input, lang)),
82+
v.transform((input) => removeNumericFormatting(input)),
8483
v.regex(positiveDecimal, 'net-income.error.invalid'),
8584
v.transform(Number),
8685
v.number('net-income.error.invalid'),
@@ -89,23 +88,23 @@ function processIncome(formData: FormData, isMarried: boolean, lang: Language) {
8988
workingIncome: v.pipe(
9089
v.string('working-income.error.required'),
9190
v.nonEmpty('working-income.error.required'),
92-
v.transform((input) => removeNumericFormatting(input, lang)),
91+
v.transform((input) => removeNumericFormatting(input)),
9392
v.regex(positiveDecimal, 'working-income.error.invalid'),
9493
v.transform(Number),
9594
v.number('working-income.error.invalid'),
9695
v.minValue(0, 'working-income.error.invalid'),
9796
),
9897
claimedIncome: v.pipe(
9998
v.optional(v.string(), '0'),
100-
v.transform((input) => removeNumericFormatting(input, lang)),
99+
v.transform((input) => removeNumericFormatting(input)),
101100
v.regex(positiveDecimal, 'claimed-income.error.invalid'),
102101
v.transform(Number),
103102
v.number('claimed-income.error.invalid'),
104103
v.minValue(0, 'claimed-income.error.invalid'),
105104
),
106105
claimedRepayment: v.pipe(
107106
v.optional(v.string(), '0'),
108-
v.transform((input) => removeNumericFormatting(input, lang)),
107+
v.transform((input) => removeNumericFormatting(input)),
109108
v.regex(positiveDecimal, 'claimed-repayment.error.invalid'),
110109
v.transform(Number),
111110
v.number('claimed-repayment.error.invalid'),
@@ -198,7 +197,8 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
198197
</div>
199198
}
200199
defaultValue={
201-
loaderData.formValues?.individualIncome.netIncome ?? previousFormValues.get('income:individual-net-income')
200+
loaderData.formValues?.individualIncome.netIncome ??
201+
removeNumericFormatting(previousFormValues.get('income:individual-net-income'))
202202
}
203203
errorMessage={
204204
errors?.nested?.['individualIncome.netIncome']?.at(0) ? (
@@ -226,7 +226,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
226226
}
227227
defaultValue={
228228
(loaderData.formValues?.kind === 'married' ? loaderData.formValues.partnerIncome.netIncome : undefined) ??
229-
previousFormValues.get('income:partner-net-income')
229+
removeNumericFormatting(previousFormValues.get('income:partner-net-income'))
230230
}
231231
errorMessage={
232232
errors?.nested?.['partnerIncome.netIncome']?.at(0) ? (
@@ -264,7 +264,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
264264
}
265265
defaultValue={
266266
loaderData.formValues?.individualIncome.workingIncome ??
267-
previousFormValues.get('income:individual-working-income')
267+
removeNumericFormatting(previousFormValues.get('income:individual-working-income'))
268268
}
269269
errorMessage={
270270
errors?.nested?.['individualIncome.workingIncome']?.at(0) ? (
@@ -303,7 +303,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
303303
}
304304
defaultValue={
305305
(loaderData.formValues?.kind === 'married' ? loaderData.formValues.partnerIncome.workingIncome : undefined) ??
306-
previousFormValues.get('income:partner-working-income')
306+
removeNumericFormatting(previousFormValues.get('income:partner-working-income'))
307307
}
308308
errorMessage={
309309
errors?.nested?.['partnerIncome.workingIncome']?.at(0) ? (
@@ -340,7 +340,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
340340
}
341341
defaultValue={
342342
loaderData.formValues?.individualIncome.claimedIncome ??
343-
previousFormValues.get('income:individual-claimed-income')
343+
removeNumericFormatting(previousFormValues.get('income:individual-claimed-income'))
344344
}
345345
errorMessage={
346346
errors?.nested?.['individualIncome.claimedIncome']?.at(0) ? (
@@ -375,7 +375,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
375375
}
376376
defaultValue={
377377
loaderData.formValues?.individualIncome.claimedRepayment ??
378-
previousFormValues.get('income:individual-claimed-repayment')
378+
removeNumericFormatting(previousFormValues.get('income:individual-claimed-repayment'))
379379
}
380380
errorMessage={
381381
errors?.nested?.['individualIncome.claimedRepayment']?.at(0) ? (
@@ -411,7 +411,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
411411
}
412412
defaultValue={
413413
(loaderData.formValues?.kind === 'married' ? loaderData.formValues.partnerIncome.claimedIncome : undefined) ??
414-
previousFormValues.get('income:partner-claimed-income')
414+
removeNumericFormatting(previousFormValues.get('income:partner-claimed-income'))
415415
}
416416
errorMessage={
417417
errors?.nested?.['partnerIncome.claimedIncome']?.at(0) ? (
@@ -449,7 +449,7 @@ export default function StepIncome({ actionData, loaderData, matches, params }:
449449
defaultValue={
450450
(loaderData.formValues?.kind === 'married'
451451
? loaderData.formValues.partnerIncome.claimedRepayment
452-
: undefined) ?? previousFormValues.get('income:partner-claimed-repayment')
452+
: undefined) ?? removeNumericFormatting(previousFormValues.get('income:partner-claimed-repayment'))
453453
}
454454
errorMessage={
455455
errors?.nested?.['partnerIncome.claimedRepayment']?.at(0) ? (

frontend/app/utils/string-utils.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @param input string represenation of a formatted decimal (examples: en:"1,234.56", fr:"1 234,56")
44
* @param lang Format language (en or fr)
55
* @returns string representation of the decimal without formatting (examples: "1234.56")
6-
*/
6+
*
77
export function removeNumericFormatting(input: string, lang: Language): string {
88
switch (lang) {
99
case 'en': {
@@ -16,6 +16,24 @@ export function removeNumericFormatting(input: string, lang: Language): string {
1616
}
1717
}
1818
}
19+
*/
20+
export function removeNumericFormatting(input: string | undefined): string {
21+
if (input === undefined) return '';
22+
23+
const isFrench = input.includes(' ') || input.lastIndexOf(',') >= input.length - 3;
24+
const isEnglish = input.indexOf(',') < input.length - 2 || input.lastIndexOf('.') >= input.length - 3;
25+
26+
if (isFrench) {
27+
const output = input.replaceAll(' ', '').replaceAll(',', '.');
28+
return output;
29+
}
30+
if (isEnglish) {
31+
const output = input.replaceAll(',', '');
32+
return output;
33+
}
34+
35+
return input;
36+
}
1937

2038
/**
2139
* Generate a random string using the provided characters, or alphanumeric characters if none are provided.

frontend/tests/utils/string-utils.test.ts

+26-11
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,33 @@ describe('string-utils', () => {
1717
});
1818

1919
it.each([
20-
{ input: '.00', output: '.00', lang: 'en' },
21-
{ input: '0.00', output: '0.00', lang: 'en' },
22-
{ input: '1,234.56', output: '1234.56', lang: 'en' },
23-
{ input: '123.56', output: '123.56', lang: 'en' },
24-
{ input: ',00', output: '.00', lang: 'fr' },
25-
{ input: '0,00', output: '0.00', lang: 'fr' },
26-
{ input: '1 234,56', output: '1234.56', lang: 'fr' },
27-
{ input: '123,56', output: '123.56', lang: 'fr' },
20+
{ input: '.00', output: '.00' },
21+
{ input: '0.00', output: '0.00' },
22+
{ input: '1,234.56', output: '1234.56' },
23+
{ input: '123.56', output: '123.56' },
24+
{ input: ',00', output: '.00' },
25+
{ input: '0,00', output: '0.00' },
26+
{ input: '1 234,56', output: '1234.56' },
27+
{ input: '123,56', output: '123.56' },
28+
{ input: '1 234', output: '1234' },
29+
{ input: '1,234', output: '1234' },
30+
{ input: '1,234,567', output: '1234567' },
31+
{ input: '1 234 567', output: '1234567' },
32+
{ input: '1', output: '1' },
33+
{ input: '12', output: '12' },
34+
{ input: '123', output: '123' },
35+
{ input: '1.1', output: '1.1' },
36+
{ input: '1,1', output: '1.1' },
37+
{ input: '1,12', output: '1.12' },
38+
{ input: '1.12', output: '1.12' },
39+
{ input: '123.12', output: '123.12' },
40+
{ input: '123,12', output: '123.12' },
41+
{ input: '1,234.12', output: '1234.12' },
42+
{ input: '1 234,12', output: '1234.12' },
2843
])(
29-
'removeNumericFormatting should remove formatting from a formatted decimal string representation',
30-
({ input, lang, output }) => {
31-
const str = removeNumericFormatting(input, lang as Language);
44+
'removeNumericFormatting should remove formatting from a formatted decimal string representation ($input)',
45+
({ input, output }) => {
46+
const str = removeNumericFormatting(input);
3247
expect(str).toBe(output);
3348
},
3449
);

0 commit comments

Comments
 (0)