Skip to content

Commit

Permalink
feat(PhoneNumberFieldLite): Phone number field with simple formatting…
Browse files Browse the repository at this point in the history
… to reduce bundle size (#1276)

WEB-2081 

Review re-requested because E164 support has been added
Related commits:

https://github.com/Telefonica/mistica-web/pull/1276/files/59f3ec30947f358fddff18befe4e1db69726fb4d..7cb3c6f1eef8445bba2b247d1f246b2fba9c904e

---------

Co-authored-by: Pedro Ladaria <[email protected]>
  • Loading branch information
pladaria and Pedro Ladaria authored Oct 29, 2024
1 parent f891f58 commit a141b97
Show file tree
Hide file tree
Showing 10 changed files with 550 additions and 4 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions src/__screenshot_tests__/input-fields-screenshot-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,28 @@ test.each`
expect(await fieldWrapper.screenshot()).toMatchImageSnapshot();
});

test.each`
skin | number
${'Vivo'} | ${'2145678901'}
${'Vivo'} | ${'+34654834455'}
${'Movistar'} | ${'654834455'}
`('PhoneNumberFieldLite - $number in $skin skin', async ({skin, number}) => {
await openStoryPage({
id: 'components-input-fields-phonenumberfieldlite--uncontrolled',
device: 'MOBILE_IOS',
skin,
args: {defaultValue: number},
});

const fieldWrapper = await screen.findByTestId('phone-number-field-lite');
const field = await screen.findByLabelText('Label');

await field.click({clickCount: 3});
await field.type(number);

expect(await fieldWrapper.screenshot()).toMatchImageSnapshot();
});

test('CreditCardExpirationField', async () => {
await openStoryPage({
id: 'components-input-fields-creditcardexpirationfield--uncontrolled',
Expand Down
170 changes: 170 additions & 0 deletions src/__stories__/phone-number-field-lite-story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import * as React from 'react';
import {Box, Text1, Stack, ResponsiveLayout, PhoneNumberFieldLite, Boxed} from '..';
import {inspect} from 'util';
import {phoneNumbersList} from './helpers';

export default {
title: 'Components/Input fields/PhoneNumberFieldLite',
parameters: {fullScreen: true},
};

const getPhoneNumberSuggestions = (value: string) =>
phoneNumbersList
.filter((s) => String(s).toLocaleLowerCase().startsWith(value.toLocaleLowerCase()))
.slice(0, 5);

interface PhoneNumberFieldBaseArgs {
label: string;
placeholder: string;
prefix: string;
helperText: string;
error: boolean;
inverse: boolean;
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: PhoneNumberFieldBaseArgs = {
label: 'Label',
placeholder: '',
prefix: '',
helperText: '',
error: false,
inverse: false,
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface PhoneNumberFieldControlledArgs extends PhoneNumberFieldBaseArgs {
initialValue: string;
suggestions: boolean;
}

const Description = () => {
return (
<Boxed>
<Box padding={16}>
<Stack space={16}>
<div>
This is a "light" version of the PhoneNumberField component. It does not use google's
libphonenumber library to reduce bundle size.
</div>
<Stack space={8}>
<div>- Only supported countries are formatted</div>
<div>- Not all phone number types are formatted</div>
<div>- Numbers in E164 are returned unformatted</div>
<div>
- A custom formatter can be provided via props and the formatter used by this
component is exported as `formatPhoneLite`
</div>
</Stack>
</Stack>
</Box>
</Boxed>
);
};

export const Controlled: StoryComponent<PhoneNumberFieldControlledArgs> = ({
inverse,
initialValue,
suggestions,
...rest
}) => {
const [rawValue, setRawValue] = React.useState<any>(initialValue);
const [value, setValue] = React.useState<any>(undefined);

return (
<ResponsiveLayout variant={inverse ? 'inverse' : undefined} fullWidth>
<Box padding={16}>
<Stack space={16}>
<Description />
<PhoneNumberFieldLite
value={rawValue}
onChangeValue={(value, rawValue) => {
setValue(value);
setRawValue(rawValue);
}}
name="phoneNumber"
autoComplete="off"
dataAttributes={{testid: 'phone-number-field-lite'}}
getSuggestions={suggestions ? getPhoneNumberSuggestions : undefined}
{...rest}
/>
<Stack space={8}>
<Text1 regular>
value: {typeof value === 'undefined' ? '' : `(${typeof value}) ${inspect(value)}`}
</Text1>
<Text1 regular>
rawValue:{' '}
{typeof rawValue === 'undefined'
? ''
: `(${typeof rawValue}) ${inspect(rawValue)}`}
</Text1>
</Stack>
</Stack>
</Box>
</ResponsiveLayout>
);
};

Controlled.storyName = 'controlled';
Controlled.args = {
initialValue: '654834455',
...defaultBaseArgs,
suggestions: false,
};

interface PhoneNumberFieldUncontrolledArgs extends PhoneNumberFieldBaseArgs {
defaultValue: string;
}

export const Uncontrolled: StoryComponent<PhoneNumberFieldUncontrolledArgs> = ({
inverse,
defaultValue,
...rest
}) => {
const [rawValue, setRawValue] = React.useState<any>(undefined);
const [value, setValue] = React.useState<any>(undefined);

return (
<ResponsiveLayout variant={inverse ? 'inverse' : undefined} fullWidth>
<Box padding={16}>
<Stack space={16}>
<Description />
<PhoneNumberFieldLite
defaultValue={defaultValue}
onChangeValue={(value, rawValue) => {
setValue(value);
setRawValue(rawValue);
}}
name="phoneNumber"
autoComplete="off"
dataAttributes={{testid: 'phone-number-field-lite'}}
{...rest}
/>
<Stack space={8}>
<Text1 regular>
value: {typeof value === 'undefined' ? '' : `(${typeof value}) ${inspect(value)}`}
</Text1>
<Text1 regular>
rawValue:{' '}
{typeof rawValue === 'undefined'
? ''
: `(${typeof rawValue}) ${inspect(rawValue)}`}
</Text1>
</Stack>
</Stack>
</Box>
</ResponsiveLayout>
);
};

Uncontrolled.storyName = 'uncontrolled';
Uncontrolled.args = {
defaultValue: '654834455',
...defaultBaseArgs,
};
4 changes: 2 additions & 2 deletions src/__stories__/phone-number-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const Controlled: StoryComponent<PhoneNumberFieldControlledArgs> = ({
const [value, setValue] = React.useState<any>(undefined);

return (
<ResponsiveLayout isInverse={inverse} fullWidth>
<ResponsiveLayout variant={inverse ? 'inverse' : undefined} fullWidth>
<Box padding={16}>
<Stack space={16}>
<PhoneNumberField
Expand Down Expand Up @@ -108,7 +108,7 @@ export const Uncontrolled: StoryComponent<PhoneNumberFieldUncontrolledArgs> = ({
const [value, setValue] = React.useState<any>(undefined);

return (
<ResponsiveLayout isInverse={inverse} fullWidth>
<ResponsiveLayout variant={inverse ? 'inverse' : undefined} fullWidth>
<Box padding={16}>
<Stack space={16}>
<PhoneNumberField
Expand Down
103 changes: 103 additions & 0 deletions src/__tests__/phone-number-field-lite-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {render, screen} from '@testing-library/react';
import PhoneNumberFieldLite from '../phone-number-field-lite';
import {getMovistarSkin} from '../skins/movistar';
import ThemeContextProvider from '../theme-context-provider';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import PhoneNumberField from '../phone-number-field';

test.each`
regionCode | number | expected | expectedRaw | expectedE164 | description
${'ZZ'} | ${'123456789012345'} | ${'123456789012345'} | ${'123456789012345'} | ${'123456789012345'} | ${'Unknown region'}
${'ES'} | ${'654834455'} | ${'654834455'} | ${'654 83 44 55'} | ${'+34654834455'} | ${'ES mobile'}
${'ES'} | ${'914-44/10 25'} | ${'914441025'} | ${'914 44 10 25'} | ${'+34914441025'} | ${'ES landline'}
${'ES'} | ${'6548344556'} | ${'6548344556'} | ${'6548344556'} | ${'+346548344556'} | ${'ES mobile too long'}
${'ES'} | ${'914-44/10 256'} | ${'9144410256'} | ${'9144410256'} | ${'+349144410256'} | ${'ES landline too long'}
${'ES'} | ${'+34 654 834 455'} | ${'+34654834455'} | ${'+34 654 83 44 55'} | ${'+34654834455'} | ${'ES E164 mobile'}
${'ES'} | ${'+34 914-44/10 25'} | ${'+34914441025'} | ${'+34 914 44 10 25'} | ${'+34914441025'} | ${'ES E164 landline'}
${'BR'} | ${'21987654321'} | ${'21987654321'} | ${'(21) 98765-4321'} | ${'+5521987654321'} | ${'BR mobile'}
${'BR'} | ${'219876543210'} | ${'219876543210'} | ${'219876543210'} | ${'+55219876543210'} | ${'BR mobile too long'}
${'BR'} | ${'2123456789'} | ${'2123456789'} | ${'(21) 2345-6789'} | ${'+552123456789'} | ${'BR landline'}
${'BR'} | ${'21234567890'} | ${'21234567890'} | ${'(21) 23456-7890'} | ${'+5521234567890'} | ${'BR landline long'}
${'BR'} | ${'212345678901'} | ${'212345678901'} | ${'212345678901'} | ${'+55212345678901'} | ${'BR landline too long'}
${'BR'} | ${'+5521987654321'} | ${'+5521987654321'} | ${'+55 21 98765-4321'} | ${'+5521987654321'} | ${'BR E164 mobile'}
${'BR'} | ${'+34654834455'} | ${'+34654834455'} | ${'+34 654 83 44 55'} | ${'+34654834455'} | ${'BR with ES E164'}
${'DE'} | ${'015789012345'} | ${'015789012345'} | ${'01578 9012345'} | ${'+4915789012345'} | ${'DE mobile 15'}
${'DE'} | ${'01601234567'} | ${'01601234567'} | ${'0160 1234567'} | ${'+491601234567'} | ${'DE mobile 16'}
${'DE'} | ${'01701234567'} | ${'01701234567'} | ${'0170 1234567'} | ${'+491701234567'} | ${'DE mobile 17'}
${'DE'} | ${'12345678901'} | ${'12345678901'} | ${'12345678901'} | ${'+4912345678901'} | ${'DE unknown'}
${'DE'} | ${'+4915789012345'} | ${'+4915789012345'} | ${'+49 1578 9012345'} | ${'+4915789012345'} | ${'DE E164 mobile'}
${'DE'} | ${'+49015789012345'} | ${'+49015789012345'} | ${'+49 015789012345'} | ${'+49015789012345'} | ${'DE E164 mobile wrong zero'}
${'GB'} | ${'07123456789'} | ${'07123456789'} | ${'07123 456789'} | ${'+447123456789'} | ${'GB mobile'}
${'GB'} | ${'071234567890'} | ${'071234567890'} | ${'071234567890'} | ${'+4471234567890'} | ${'GB mobile too long'}
${'GB'} | ${'+447123456789'} | ${'+447123456789'} | ${'+44 7123 456789'} | ${'+447123456789'} | ${'GB E164 mobile'}
${'GB'} | ${'+4407123456789'} | ${'+4407123456789'} | ${'+44 07123456789'} | ${'+4407123456789'} | ${'GB E164 mobile wrong zero'}
`(
'PhoneNumberFieldLite - $description - $number',
async ({regionCode, number, expected, expectedRaw, expectedE164}) => {
const onChangeValue = jest.fn();
const onChangeValueE164 = jest.fn();
const onChangeValueUsingLibphonenumber = jest.fn();

render(
<ThemeContextProvider
theme={{
skin: getMovistarSkin(),
i18n: {locale: 'es-ES', phoneNumberFormattingRegionCode: regionCode},
}}
>
<PhoneNumberFieldLite name="phone" label="Phone" onChangeValue={onChangeValue} />
<PhoneNumberFieldLite name="e164" label="Phone E164" onChangeValue={onChangeValueE164} e164 />
<PhoneNumberField
name="ref"
label="Reference"
onChangeValue={onChangeValueUsingLibphonenumber}
/>
</ThemeContextProvider>
);

const input = screen.getByLabelText('Phone');
const inputE164 = screen.getByLabelText('Phone E164');
const referenceInput = screen.getByLabelText('Reference');

await userEvent.type(input, number);
await userEvent.type(inputE164, number);
await userEvent.type(referenceInput, number);

expect(onChangeValue).toHaveBeenLastCalledWith(expected, expectedRaw);
expect(onChangeValueE164).toHaveBeenLastCalledWith(expectedE164, expectedRaw);

// We expect the same result as the libphonenumber version, except for the E164 format
if (!number.startsWith('+')) {
// This checks all the calls to onChangeValue (as you type)
expect(onChangeValue.mock.calls).toEqual(onChangeValueUsingLibphonenumber.mock.calls);
}
}
);

test('PhoneNumberFieldLite custom formatter', async () => {
const onChangeValue = jest.fn();

render(
<ThemeContextProvider
theme={{
skin: getMovistarSkin(),
i18n: {locale: 'es-ES', phoneNumberFormattingRegionCode: 'ES'},
}}
>
<PhoneNumberFieldLite
name="a"
label="Phone"
onChangeValue={onChangeValue}
format={(number) => {
return number.replace(/\D/g, '').split('').join('-');
}}
/>
</ThemeContextProvider>
);

const input = screen.getByLabelText('Phone');
await userEvent.type(input, '654834455');

expect(onChangeValue).toHaveBeenLastCalledWith('654834455', '6-5-4-8-3-4-4-5-5');
});
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export {TextFieldBase} from './text-field-base';
export {default as SearchField} from './search-field';
export {default as EmailField} from './email-field';
export {default as PhoneNumberField} from './phone-number-field';
export {default as PhoneNumberFieldLite, formatPhoneLite} from './phone-number-field-lite';
export {default as CreditCardNumberField} from './credit-card-number-field';
export {default as CreditCardExpirationField} from './credit-card-expiration-field';
export {default as CreditCardFields} from './credit-card-fields';
Expand Down
Loading

1 comment on commit a141b97

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for mistica-web ready!

✅ Preview
https://mistica-24d59x3u2-flows-projects-65bb050e.vercel.app

Built with commit a141b97.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.