Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
16eabf7
Fix: Resolve MISSING TRANSLATION error in country picker for legacy data
abzokhattab Sep 18, 2025
d67d2f3
refactoring the getCountryCode function
abzokhattab Sep 18, 2025
3d40b2d
fixing eslint
abzokhattab Sep 18, 2025
7e0d4a6
fixing spellcheck
abzokhattab Sep 18, 2025
5166a66
fixing eslint
abzokhattab Sep 18, 2025
0d4c5f6
Merge remote-tracking branch 'origin/main' into fix/country-picker-mi…
abzokhattab Sep 23, 2025
83e5de7
Merge remote-tracking branch 'origin/main' into fix/country-picker-mi…
abzokhattab Sep 28, 2025
352e473
fix tranlsation errs of private_personalDetails.addresses
abzokhattab Sep 28, 2025
3654b18
fix eslint
abzokhattab Sep 28, 2025
6103255
fixing eslint
abzokhattab Sep 28, 2025
298d8c5
fixing eslint
abzokhattab Sep 28, 2025
fddd420
eslint fix
abzokhattab Sep 28, 2025
1fdd0dd
making getcountrycodes at the default values of usestates
abzokhattab Sep 29, 2025
9c798d8
fixing eslint
abzokhattab Sep 29, 2025
7be77e5
remove getCountryCode from Address and Addresspage and add it to the …
abzokhattab Oct 3, 2025
1196f11
minor edit
abzokhattab Oct 3, 2025
48b53e5
adding getCountryCode to MissingPersonalDetailsContent
abzokhattab Oct 3, 2025
41e1bac
using a common hook
abzokhattab Oct 3, 2025
a223492
fixing eslint
abzokhattab Oct 3, 2025
df2d38e
fixing eslint
abzokhattab Oct 3, 2025
61292b4
Merge remote-tracking branch 'origin/main' into fix/country-picker-mi…
abzokhattab Oct 4, 2025
a6f2b60
remvoing hook and using util funciton instead
abzokhattab Oct 5, 2025
400645a
fixing eslint
abzokhattab Oct 5, 2025
97804d3
fixing eslint types
abzokhattab Oct 5, 2025
7f3c67b
fixing eslint
abzokhattab Oct 5, 2025
329141c
fixing types
abzokhattab Oct 5, 2025
b335622
adding tests for normalizeCountryCode
abzokhattab Oct 6, 2025
89724b4
fixing cspell
abzokhattab Oct 6, 2025
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
39 changes: 39 additions & 0 deletions src/libs/CountryUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
import type {PersonalDetailsForm} from '@src/types/form';
import type {Address} from '@src/types/onyx/PrivatePersonalDetails';

type AddressType = PersonalDetailsForm | Address | undefined;

/**
* Normalizes the address containing a country field by converting country names to country codes.
* Handles the case where old data has "United States" instead of "US".
*/
function normalizeCountryCode(data: AddressType): AddressType {
if (!data?.country) {
return data;
}

const normalizedCountry = getCountryCode(data.country);

if (!normalizedCountry) {
return data;
}

return {
...data,
country: normalizedCountry,
};
}

function getCountryCode(countryValue: string | undefined): Country | undefined {
for (const [code, name] of Object.entries(CONST.ALL_COUNTRIES)) {
if (name === countryValue) {
return code as Country;
}
}

return countryValue as Country | undefined;
}

export {normalizeCountryCode, getCountryCode};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import useSubStep from '@hooks/useSubStep';
import useThemeStyles from '@hooks/useThemeStyles';
import {clearDraftValues} from '@libs/actions/FormActions';
import {updatePersonalDetailsAndShipExpensifyCards} from '@libs/actions/PersonalDetails';
import {normalizeCountryCode} from '@libs/CountryUtils';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -41,7 +42,7 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues}: Mi

const ref: ForwardedRef<InteractiveStepSubHeaderHandle> = useRef(null);

const values = useMemo(() => getSubstepValues(privatePersonalDetails, draftValues), [privatePersonalDetails, draftValues]);
const values = useMemo(() => normalizeCountryCode(getSubstepValues(privatePersonalDetails, draftValues)) as PersonalDetailsForm, [privatePersonalDetails, draftValues]);

const startFrom = useMemo(() => getInitialSubstep(values), [values]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, {useMemo} from 'react';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import {normalizeCountryCode} from '@libs/CountryUtils';
import {getCurrentAddress} from '@libs/PersonalDetailsUtils';
import AddressPage from '@pages/AddressPage';
import type {FormOnyxValues} from '@src/components/Form/types';
Expand Down Expand Up @@ -33,7 +34,7 @@ function PersonalAddressPage() {
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: true});
const [defaultCountry, defaultCountryStatus] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: true});
const isLoading = isLoadingOnyxValue(defaultCountryStatus);
const address = useMemo(() => getCurrentAddress(privatePersonalDetails), [privatePersonalDetails]);
const address = useMemo(() => normalizeCountryCode(getCurrentAddress(privatePersonalDetails)) as Address, [privatePersonalDetails]);
if (isLoading) {
return <FullScreenLoadingIndicator />;
}
Expand Down
153 changes: 153 additions & 0 deletions tests/unit/CountryUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {getCountryCode, normalizeCountryCode} from '@libs/CountryUtils';
import type {Address} from '@src/types/onyx/PrivatePersonalDetails';

describe('CountryUtils', () => {
describe('getCountryCode', () => {
Comment thread
abzokhattab marked this conversation as resolved.
it('should return the same value if it is already a valid country code', () => {
expect(getCountryCode('US')).toBe('US');
expect(getCountryCode('CA')).toBe('CA');
expect(getCountryCode('GB')).toBe('GB');
expect(getCountryCode('EG')).toBe('EG');
});

it('should return the country code when given a country name', () => {
expect(getCountryCode('United States')).toBe('US');
expect(getCountryCode('Canada')).toBe('CA');
expect(getCountryCode('United Kingdom')).toBe('GB');
expect(getCountryCode('Egypt')).toBe('EG');
});

it('should return original value for invalid country names or codes', () => {
expect(getCountryCode('Invalid Country')).toBe('Invalid Country');
expect(getCountryCode('XX')).toBe('XX');
expect(getCountryCode('123')).toBe('123');
expect(getCountryCode('MISSING TRANSLATION')).toBe('MISSING TRANSLATION');
});

it('should handle edge cases with special characters', () => {
expect(getCountryCode('Bosnia & Herzegovina')).toBe('BA');
});

it('should be case sensitive for country names', () => {
expect(getCountryCode('united states')).toBe('united states');
expect(getCountryCode('UNITED STATES')).toBe('UNITED STATES');
expect(getCountryCode('United States')).toBe('US');
});

it('should convert common country names to codes', () => {
expect(getCountryCode('United States')).toBe('US');
});

it('should handle multiple country formats correctly', () => {
const testCases = [
{name: 'Afghanistan', code: 'AF'},
{name: 'Australia', code: 'AU'},
{name: 'Brazil', code: 'BR'},
{name: 'China', code: 'CN'},
{name: 'France', code: 'FR'},
{name: 'Germany', code: 'DE'},
{name: 'India', code: 'IN'},
{name: 'Japan', code: 'JP'},
{name: 'Mexico', code: 'MX'},
{name: 'Russia', code: 'RU'},
];

testCases.forEach(({name, code}) => {
expect(getCountryCode(name)).toBe(code);
expect(getCountryCode(code)).toBe(code);
});
});
});

describe('normalizeCountryCode', () => {
it('should return undefined when data is undefined', () => {
expect(normalizeCountryCode(undefined)).toBeUndefined();
});

it('should return data unchanged when country field is missing', () => {
const data = {street: '123 Main St', city: 'New York', state: 'NY'};
expect(normalizeCountryCode(data)).toEqual(data);
});

it('should return data unchanged when country is undefined', () => {
const data = {street: '123 Main St', city: 'New York', state: 'NY', country: undefined};
expect(normalizeCountryCode(data)).toEqual(data);
});

it('should convert country name to country code', () => {
const data = {street: '123 Main St', city: 'New York', state: 'NY', country: 'United States'} as unknown as Address;
const result = normalizeCountryCode(data);
expect(result).toEqual({street: '123 Main St', city: 'New York', state: 'NY', country: 'US'});
});

it('should preserve country code if already a valid code', () => {
const data: Address = {street: '456 Oak Ave', city: 'Toronto', state: 'ON', country: 'CA'};
const result = normalizeCountryCode(data);
expect(result).toEqual({street: '456 Oak Ave', city: 'Toronto', state: 'ON', country: 'CA'});
});

it('should handle multiple country name conversions', () => {
const testCases = [
{input: 'United States', expected: 'US'},
{input: 'Canada', expected: 'CA'},
{input: 'United Kingdom', expected: 'GB'},
{input: 'Germany', expected: 'DE'},
{input: 'France', expected: 'FR'},
{input: 'Japan', expected: 'JP'},
{input: 'Australia', expected: 'AU'},
];

testCases.forEach(({input, expected}) => {
const data = {street: '789 Test St', city: 'Test City', country: input} as unknown as Address;
const result = normalizeCountryCode(data);
expect(result?.country).toBe(expected);
});
});

it('should preserve invalid country values', () => {
const data = {street: '789 Test St', city: 'Test City', country: 'Invalid Country'} as unknown as Address;
const result = normalizeCountryCode(data);
expect(result).toEqual({street: '789 Test St', city: 'Test City', country: 'Invalid Country'});
});

it('should handle special characters in country names', () => {
const data = {street: '123 Main St', city: 'Sarajevo', country: 'Bosnia & Herzegovina'} as unknown as Address;
const result = normalizeCountryCode(data);
expect(result).toEqual({street: '123 Main St', city: 'Sarajevo', country: 'BA'});
});

it('should be case sensitive when normalizing country names', () => {
const dataLowerCase = {street: '789 Test St', city: 'Test City', country: 'united states'} as unknown as Address;
const dataUpperCase = {street: '789 Test St', city: 'Test City', country: 'UNITED STATES'} as unknown as Address;
const dataProperCase = {street: '789 Test St', city: 'Test City', country: 'United States'} as unknown as Address;

expect(normalizeCountryCode(dataLowerCase)).toEqual({street: '789 Test St', city: 'Test City', country: 'united states'});
expect(normalizeCountryCode(dataUpperCase)).toEqual({street: '789 Test St', city: 'Test City', country: 'UNITED STATES'});
expect(normalizeCountryCode(dataProperCase)).toEqual({street: '789 Test St', city: 'Test City', country: 'US'});
});

it('should preserve all other fields in the data object', () => {
const data = {
street: '123 Main St',
city: 'Los Angeles',
state: 'CA',
zipCode: '90001',
country: 'United States',
} as unknown as Address;
const result = normalizeCountryCode(data);
expect(result).toEqual({
street: '123 Main St',
city: 'Los Angeles',
state: 'CA',
zipCode: '90001',
country: 'US',
});
});

it('should handle MISSING TRANSLATION value', () => {
const data = {street: '789 Test St', city: 'Test City', country: 'MISSING TRANSLATION'} as unknown as Address;
const result = normalizeCountryCode(data);
expect(result).toEqual({street: '789 Test St', city: 'Test City', country: 'MISSING TRANSLATION'});
});
});
});
Loading