@transferwise/components 0.0.0-experimental-468b85f → 0.0.0-experimental-ca51404
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/avatarLayout/AvatarLayout.js +1 -0
- package/build/avatarLayout/AvatarLayout.js.map +1 -1
- package/build/avatarLayout/AvatarLayout.mjs +1 -0
- package/build/avatarLayout/AvatarLayout.mjs.map +1 -1
- package/build/types/avatarLayout/AvatarLayout.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/avatarLayout/AvatarLayout.story.tsx +2 -0
- package/src/avatarLayout/AvatarLayout.tsx +3 -1
- package/src/phoneNumberInput/PhoneNumberInput.rtl.spec.tsx +32 -0
- package/src/phoneNumberInput/PhoneNumberInput.spec.js +356 -0
- package/src/phoneNumberInput/PhoneNumberInput.spec.tsx +0 -300
|
@@ -17,6 +17,7 @@ function AvatarLayout({
|
|
|
17
17
|
const avatarSize = isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size]?.avatarSize : size;
|
|
18
18
|
return avatars.length < 1 ? null : avatars.length === 1 ? /*#__PURE__*/jsxRuntime.jsx(AvatarView, {
|
|
19
19
|
...avatars[0],
|
|
20
|
+
size: size,
|
|
20
21
|
children: avatars[0].asset
|
|
21
22
|
}) : /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
22
23
|
className: clsx.clsx('np-avatar-layout', `np-avatar-layout-${orientation}`, className),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AvatarLayout.js","sources":["../../src/avatarLayout/AvatarLayout.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport AvatarView, { AvatarViewProps } from '../avatarView';\n\ntype SingleAvatarType = { asset?: AvatarViewProps['children'] } & Omit<\n AvatarViewProps,\n 'notification' | 'selected' | 'size' | 'badge' | 'children' | 'interactive'\n>;\n\nexport type Props = {\n avatars: SingleAvatarType[];\n orientation?: 'horizontal' | 'diagonal';\n} & Pick<\n AvatarViewProps,\n 'size' | 'interactive' | 'className' | 'role' | 'aria-label' | 'aria-labelledby' | 'aria-hidden'\n>;\n\nexport default function AvatarLayout({\n avatars = [],\n orientation: orientationProp = 'horizontal',\n size = 48,\n className,\n interactive,\n ...restProps\n}: Props) {\n const orientation =\n size === 16 && orientationProp === 'diagonal' ? 'horizontal' : orientationProp;\n const isDiagonal = orientation === 'diagonal';\n const avatarSize = isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size]?.avatarSize : size;\n return avatars.length < 1 ? null : avatars.length === 1 ? (\n <AvatarView {...avatars[0]}
|
|
1
|
+
{"version":3,"file":"AvatarLayout.js","sources":["../../src/avatarLayout/AvatarLayout.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport AvatarView, { AvatarViewProps } from '../avatarView';\n\ntype SingleAvatarType = { asset?: AvatarViewProps['children'] } & Omit<\n AvatarViewProps,\n 'notification' | 'selected' | 'size' | 'badge' | 'children' | 'interactive'\n>;\n\nexport type Props = {\n avatars: SingleAvatarType[];\n orientation?: 'horizontal' | 'diagonal';\n} & Pick<\n AvatarViewProps,\n 'size' | 'interactive' | 'className' | 'role' | 'aria-label' | 'aria-labelledby' | 'aria-hidden'\n>;\n\nexport default function AvatarLayout({\n avatars = [],\n orientation: orientationProp = 'horizontal',\n size = 48,\n className,\n interactive,\n ...restProps\n}: Props) {\n const orientation =\n size === 16 && orientationProp === 'diagonal' ? 'horizontal' : orientationProp;\n const isDiagonal = orientation === 'diagonal';\n const avatarSize = isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size]?.avatarSize : size;\n return avatars.length < 1 ? null : avatars.length === 1 ? (\n <AvatarView {...avatars[0]} size={size}>\n {avatars[0].asset}\n </AvatarView>\n ) : (\n <div\n className={clsx('np-avatar-layout', `np-avatar-layout-${orientation}`, className)}\n style={{\n // @ts-expect-error CSS custom props allowed\n '--np-avatar-layout-size': `${size}px`,\n '--np-avatar-size': `${avatarSize}px`,\n '--np-avatar-offset': `${isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size].offset : HORIZONTAL_LAYOUT_OFFSET[size]}px`,\n }}\n {...restProps}\n >\n {avatars.map(({ asset, style, ...avatar }, index) => (\n <div\n // eslint-disable-next-line react/no-array-index-key\n key={index}\n className={clsx(\n { [`np-avatar-layout-${orientation}-child`]: index !== 0 },\n { [`np-avatar-layout-${orientation}-mask`]: index !== avatars.length - 1 },\n )}\n >\n <AvatarView\n {...avatar}\n size={avatarSize as Props['size']}\n style={{\n ...(isDiagonal && {\n '--circle-size': `${avatarSize}px`,\n '--circle-icon-size': `${DIAGONAL_LAYOUT_STYLE_CONFIG[size].iconSize}px`,\n '--circle-font-size': `${DIAGONAL_LAYOUT_STYLE_CONFIG[size].fontSize}px`,\n }),\n ...style,\n }}\n interactive={interactive}\n >\n {asset}\n </AvatarView>\n </div>\n ))}\n </div>\n );\n}\n\n/** Diagonal layout have custom sizes for avatar, font and icon */\nconst DIAGONAL_LAYOUT_STYLE_CONFIG = {\n // Diagonal layout doesn't support 16 size\n 16: { avatarSize: undefined, offset: undefined, fontSize: undefined, iconSize: undefined },\n 24: { avatarSize: 15, offset: 2.5, fontSize: 8, iconSize: 11.25 },\n 32: { avatarSize: 20, offset: 2.5, fontSize: 12, iconSize: 15 },\n 40: { avatarSize: 24, offset: 4.5, fontSize: 12, iconSize: 18 },\n 48: { avatarSize: 30, offset: 3.5, fontSize: 14, iconSize: 16.87 },\n 56: { avatarSize: 34, offset: 5, fontSize: 14, iconSize: 19.12 },\n 72: { avatarSize: 44, offset: 6, fontSize: 22, iconSize: 22 },\n};\n\n/** Horizontal layout have custom offset between avatars */\nconst HORIZONTAL_LAYOUT_OFFSET = {\n 16: 2,\n 24: 2,\n 32: 4,\n 40: 4,\n 48: 4,\n 56: 5,\n 72: 5,\n};\n"],"names":["AvatarLayout","avatars","orientation","orientationProp","size","className","interactive","restProps","isDiagonal","avatarSize","DIAGONAL_LAYOUT_STYLE_CONFIG","length","_jsx","AvatarView","children","asset","clsx","style","offset","HORIZONTAL_LAYOUT_OFFSET","map","avatar","index","iconSize","fontSize","undefined"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ function AvatarLayout({
|
|
|
15
15
|
const avatarSize = isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size]?.avatarSize : size;
|
|
16
16
|
return avatars.length < 1 ? null : avatars.length === 1 ? /*#__PURE__*/jsx(AvatarView, {
|
|
17
17
|
...avatars[0],
|
|
18
|
+
size: size,
|
|
18
19
|
children: avatars[0].asset
|
|
19
20
|
}) : /*#__PURE__*/jsx("div", {
|
|
20
21
|
className: clsx('np-avatar-layout', `np-avatar-layout-${orientation}`, className),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AvatarLayout.mjs","sources":["../../src/avatarLayout/AvatarLayout.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport AvatarView, { AvatarViewProps } from '../avatarView';\n\ntype SingleAvatarType = { asset?: AvatarViewProps['children'] } & Omit<\n AvatarViewProps,\n 'notification' | 'selected' | 'size' | 'badge' | 'children' | 'interactive'\n>;\n\nexport type Props = {\n avatars: SingleAvatarType[];\n orientation?: 'horizontal' | 'diagonal';\n} & Pick<\n AvatarViewProps,\n 'size' | 'interactive' | 'className' | 'role' | 'aria-label' | 'aria-labelledby' | 'aria-hidden'\n>;\n\nexport default function AvatarLayout({\n avatars = [],\n orientation: orientationProp = 'horizontal',\n size = 48,\n className,\n interactive,\n ...restProps\n}: Props) {\n const orientation =\n size === 16 && orientationProp === 'diagonal' ? 'horizontal' : orientationProp;\n const isDiagonal = orientation === 'diagonal';\n const avatarSize = isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size]?.avatarSize : size;\n return avatars.length < 1 ? null : avatars.length === 1 ? (\n <AvatarView {...avatars[0]}
|
|
1
|
+
{"version":3,"file":"AvatarLayout.mjs","sources":["../../src/avatarLayout/AvatarLayout.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport AvatarView, { AvatarViewProps } from '../avatarView';\n\ntype SingleAvatarType = { asset?: AvatarViewProps['children'] } & Omit<\n AvatarViewProps,\n 'notification' | 'selected' | 'size' | 'badge' | 'children' | 'interactive'\n>;\n\nexport type Props = {\n avatars: SingleAvatarType[];\n orientation?: 'horizontal' | 'diagonal';\n} & Pick<\n AvatarViewProps,\n 'size' | 'interactive' | 'className' | 'role' | 'aria-label' | 'aria-labelledby' | 'aria-hidden'\n>;\n\nexport default function AvatarLayout({\n avatars = [],\n orientation: orientationProp = 'horizontal',\n size = 48,\n className,\n interactive,\n ...restProps\n}: Props) {\n const orientation =\n size === 16 && orientationProp === 'diagonal' ? 'horizontal' : orientationProp;\n const isDiagonal = orientation === 'diagonal';\n const avatarSize = isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size]?.avatarSize : size;\n return avatars.length < 1 ? null : avatars.length === 1 ? (\n <AvatarView {...avatars[0]} size={size}>\n {avatars[0].asset}\n </AvatarView>\n ) : (\n <div\n className={clsx('np-avatar-layout', `np-avatar-layout-${orientation}`, className)}\n style={{\n // @ts-expect-error CSS custom props allowed\n '--np-avatar-layout-size': `${size}px`,\n '--np-avatar-size': `${avatarSize}px`,\n '--np-avatar-offset': `${isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size].offset : HORIZONTAL_LAYOUT_OFFSET[size]}px`,\n }}\n {...restProps}\n >\n {avatars.map(({ asset, style, ...avatar }, index) => (\n <div\n // eslint-disable-next-line react/no-array-index-key\n key={index}\n className={clsx(\n { [`np-avatar-layout-${orientation}-child`]: index !== 0 },\n { [`np-avatar-layout-${orientation}-mask`]: index !== avatars.length - 1 },\n )}\n >\n <AvatarView\n {...avatar}\n size={avatarSize as Props['size']}\n style={{\n ...(isDiagonal && {\n '--circle-size': `${avatarSize}px`,\n '--circle-icon-size': `${DIAGONAL_LAYOUT_STYLE_CONFIG[size].iconSize}px`,\n '--circle-font-size': `${DIAGONAL_LAYOUT_STYLE_CONFIG[size].fontSize}px`,\n }),\n ...style,\n }}\n interactive={interactive}\n >\n {asset}\n </AvatarView>\n </div>\n ))}\n </div>\n );\n}\n\n/** Diagonal layout have custom sizes for avatar, font and icon */\nconst DIAGONAL_LAYOUT_STYLE_CONFIG = {\n // Diagonal layout doesn't support 16 size\n 16: { avatarSize: undefined, offset: undefined, fontSize: undefined, iconSize: undefined },\n 24: { avatarSize: 15, offset: 2.5, fontSize: 8, iconSize: 11.25 },\n 32: { avatarSize: 20, offset: 2.5, fontSize: 12, iconSize: 15 },\n 40: { avatarSize: 24, offset: 4.5, fontSize: 12, iconSize: 18 },\n 48: { avatarSize: 30, offset: 3.5, fontSize: 14, iconSize: 16.87 },\n 56: { avatarSize: 34, offset: 5, fontSize: 14, iconSize: 19.12 },\n 72: { avatarSize: 44, offset: 6, fontSize: 22, iconSize: 22 },\n};\n\n/** Horizontal layout have custom offset between avatars */\nconst HORIZONTAL_LAYOUT_OFFSET = {\n 16: 2,\n 24: 2,\n 32: 4,\n 40: 4,\n 48: 4,\n 56: 5,\n 72: 5,\n};\n"],"names":["AvatarLayout","avatars","orientation","orientationProp","size","className","interactive","restProps","isDiagonal","avatarSize","DIAGONAL_LAYOUT_STYLE_CONFIG","length","_jsx","AvatarView","children","asset","clsx","style","offset","HORIZONTAL_LAYOUT_OFFSET","map","avatar","index","iconSize","fontSize","undefined"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AvatarLayout.d.ts","sourceRoot":"","sources":["../../../src/avatarLayout/AvatarLayout.tsx"],"names":[],"mappings":"AACA,OAAmB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE5D,KAAK,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,IAAI,CACpE,eAAe,EACf,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,aAAa,CAC5E,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;CACzC,GAAG,IAAI,CACN,eAAe,EACf,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,YAAY,GAAG,iBAAiB,GAAG,aAAa,CACjG,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,OAAY,EACZ,WAAW,EAAE,eAA8B,EAC3C,IAAS,EACT,SAAS,EACT,WAAW,EACX,GAAG,SAAS,EACb,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"AvatarLayout.d.ts","sourceRoot":"","sources":["../../../src/avatarLayout/AvatarLayout.tsx"],"names":[],"mappings":"AACA,OAAmB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE5D,KAAK,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,IAAI,CACpE,eAAe,EACf,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,aAAa,CAC5E,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;CACzC,GAAG,IAAI,CACN,eAAe,EACf,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,YAAY,GAAG,iBAAiB,GAAG,aAAa,CACjG,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,OAAY,EACZ,WAAW,EAAE,eAA8B,EAC3C,IAAS,EACT,SAAS,EACT,WAAW,EACX,GAAG,SAAS,EACb,EAAE,KAAK,sCAgDP"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transferwise/components",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-ca51404",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
"rollup": "^4.18.1",
|
|
92
92
|
"rollup-preserve-directives": "^1.1.1",
|
|
93
93
|
"storybook": "^8.2.2",
|
|
94
|
-
"@transferwise/less-config": "3.1.0",
|
|
95
94
|
"@transferwise/neptune-css": "14.20.1",
|
|
96
|
-
"@wise/components-theming": "1.6.1"
|
|
95
|
+
"@wise/components-theming": "1.6.1",
|
|
96
|
+
"@transferwise/less-config": "3.1.0"
|
|
97
97
|
},
|
|
98
98
|
"peerDependencies": {
|
|
99
99
|
"@transferwise/icons": "^3.13.1",
|
|
@@ -197,8 +197,10 @@ export const EdgeInstances: Story = {
|
|
|
197
197
|
<AvatarLayout orientation="diagonal" avatars={[{}]} />
|
|
198
198
|
|
|
199
199
|
<AvatarLayout orientation="diagonal" avatars={[{ profileName: 'Jay Jay' }]} />
|
|
200
|
+
<AvatarLayout orientation="diagonal" size={16} avatars={[{ profileName: 'Jay Jay' }]} />
|
|
200
201
|
|
|
201
202
|
<AvatarLayout orientation="diagonal" avatars={[{ asset: <Flag code="gb" /> }]} />
|
|
203
|
+
<AvatarLayout orientation="diagonal" size={24} avatars={[{ asset: <Flag code="gb" /> }]} />
|
|
202
204
|
|
|
203
205
|
<AvatarLayout
|
|
204
206
|
orientation="diagonal"
|
|
@@ -27,7 +27,9 @@ export default function AvatarLayout({
|
|
|
27
27
|
const isDiagonal = orientation === 'diagonal';
|
|
28
28
|
const avatarSize = isDiagonal ? DIAGONAL_LAYOUT_STYLE_CONFIG[size]?.avatarSize : size;
|
|
29
29
|
return avatars.length < 1 ? null : avatars.length === 1 ? (
|
|
30
|
-
<AvatarView {...avatars[0]}
|
|
30
|
+
<AvatarView {...avatars[0]} size={size}>
|
|
31
|
+
{avatars[0].asset}
|
|
32
|
+
</AvatarView>
|
|
31
33
|
) : (
|
|
32
34
|
<div
|
|
33
35
|
className={clsx('np-avatar-layout', `np-avatar-layout-${orientation}`, className)}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Field } from '../field/Field';
|
|
2
|
+
import { mockMatchMedia, mockResizeObserver, render, screen, within } from '../test-utils';
|
|
3
|
+
|
|
4
|
+
import PhoneNumberInput from './PhoneNumberInput';
|
|
5
|
+
|
|
6
|
+
mockMatchMedia();
|
|
7
|
+
mockResizeObserver();
|
|
8
|
+
|
|
9
|
+
describe('PhoneNumberInput', () => {
|
|
10
|
+
it('supports custom `aria-labelledby` attribute', () => {
|
|
11
|
+
render(
|
|
12
|
+
<>
|
|
13
|
+
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
|
14
|
+
<label id="prioritized-label">Prioritized label</label>
|
|
15
|
+
<PhoneNumberInput aria-labelledby="prioritized-label" onChange={() => {}} />
|
|
16
|
+
</>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(
|
|
20
|
+
within(screen.getByLabelText('Prioritized label')).getByRole('textbox'),
|
|
21
|
+
).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('supports `Field` for labeling', () => {
|
|
25
|
+
render(
|
|
26
|
+
<Field label="Phone number">
|
|
27
|
+
<PhoneNumberInput initialValue="+12345678" onChange={() => {}} />
|
|
28
|
+
</Field>,
|
|
29
|
+
);
|
|
30
|
+
expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Phone number/);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { shallow, mount } from 'enzyme';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
|
|
4
|
+
import { mockMatchMedia, mockResizeObserver } from '../test-utils';
|
|
5
|
+
|
|
6
|
+
import PhoneNumberInput from '.';
|
|
7
|
+
|
|
8
|
+
const locale = 'en-GB';
|
|
9
|
+
const formatMessage = (message) => message.defaultMessage;
|
|
10
|
+
jest.mock('react-intl', () => ({
|
|
11
|
+
...jest.requireActual('react-intl'),
|
|
12
|
+
useIntl: jest.fn(() => ({ locale, formatMessage })),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
mockMatchMedia();
|
|
16
|
+
mockResizeObserver();
|
|
17
|
+
|
|
18
|
+
const simulatePaste = (element, value) =>
|
|
19
|
+
element.simulate('paste', { nativeEvent: { clipboardData: { getData: () => value } } });
|
|
20
|
+
|
|
21
|
+
describe('Given a telephone number component', () => {
|
|
22
|
+
let select;
|
|
23
|
+
let input;
|
|
24
|
+
let component;
|
|
25
|
+
const props = { onChange: jest.fn() };
|
|
26
|
+
const PREFIX_SELECT_SELECTOR = 'SelectInput';
|
|
27
|
+
const NUMBER_SELECTOR = 'input[name="phoneNumber"]';
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
useIntl.mockReturnValue({ locale, formatMessage });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
jest.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('when initialised without a model', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
component = shallow(<PhoneNumberInput {...props} />);
|
|
40
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
41
|
+
input = component.find(NUMBER_SELECTOR);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should set prefix control to default UK value', () => {
|
|
45
|
+
expect(select.props().value).toBe('+44');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should set number control to empty', () => {
|
|
49
|
+
expect(input.prop('value')).toBe('');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should not disable the select', () => {
|
|
53
|
+
expect(select.prop('disabled')).toBeFalsy();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should not disable the input', () => {
|
|
57
|
+
expect(input.prop('disabled')).toBeFalsy();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('when a valid model is supplied', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
component = shallow(<PhoneNumberInput {...props} initialValue="+39123456789" />);
|
|
64
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
65
|
+
input = component.find(NUMBER_SELECTOR);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should set control values correctly', () => {
|
|
69
|
+
expect(select.props().value).toBe('+39');
|
|
70
|
+
expect(input.prop('value')).toBe('123456789');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('when an id is supplied', () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
component = shallow(<PhoneNumberInput id="mock-id" {...props} />);
|
|
77
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
78
|
+
input = component.find(NUMBER_SELECTOR);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should render select with undefined id', () => {
|
|
82
|
+
expect(select.prop('id')).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should render input with expected id', () => {
|
|
86
|
+
expect(input.prop('id')).toBe('mock-id');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('when an id is not supplied', () => {
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
component = shallow(<PhoneNumberInput {...props} />);
|
|
93
|
+
input = component.find(NUMBER_SELECTOR);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should render input with unspecified id', () => {
|
|
97
|
+
expect(input.prop('id')).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('when pasting', () => {
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
component = mount(<PhoneNumberInput {...props} initialValue="+39123456789" />);
|
|
104
|
+
select = () => component.find(PREFIX_SELECT_SELECTOR);
|
|
105
|
+
input = () => component.find(NUMBER_SELECTOR);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
[
|
|
109
|
+
{ number: '+36303932551', countryCode: '+36', localNumber: '303932551' },
|
|
110
|
+
{ number: '+39123456781', countryCode: '+39', localNumber: '123456781' },
|
|
111
|
+
{ number: '+44 7700 900415', countryCode: '+44', localNumber: '7700900415' },
|
|
112
|
+
{ number: '+2975557308515', countryCode: '+297', localNumber: '5557308515' },
|
|
113
|
+
{ number: '+297-555-7217-360', countryCode: '+297', localNumber: '5557217360' },
|
|
114
|
+
{ number: '+213-555-5160-67', countryCode: '+213', localNumber: '555516067' },
|
|
115
|
+
{ number: '+246-387-5553', countryCode: '+246', localNumber: '3875553' },
|
|
116
|
+
{ number: '+852-940-5558--6', countryCode: '+852', localNumber: '94055586' },
|
|
117
|
+
{ number: '+228 253 5558 4', countryCode: '+228', localNumber: '25355584' },
|
|
118
|
+
].forEach(({ number, countryCode, localNumber }) => {
|
|
119
|
+
it(`${number} code should update the value properly`, () => {
|
|
120
|
+
simulatePaste(component.find('input'), number);
|
|
121
|
+
|
|
122
|
+
expect(select().props().value).toBe(countryCode);
|
|
123
|
+
expect(input().prop('value')).toBe(localNumber);
|
|
124
|
+
expect(props.onChange).toHaveBeenCalledWith(number.replace(/(\s|-)+/g, ''), countryCode);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should not paste invalid characters', () => {
|
|
129
|
+
simulatePaste(component.find('input'), '+36asdasdasd');
|
|
130
|
+
expect(select().props().value).toBe('+39');
|
|
131
|
+
expect(input().prop('value')).toBe('123456789');
|
|
132
|
+
expect(props.onChange).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should not paste countries which are not in the select', () => {
|
|
136
|
+
simulatePaste(component.find('input'), '+9992342343423');
|
|
137
|
+
expect(select().props().value).toBe('+39');
|
|
138
|
+
expect(input().prop('value')).toBe('123456789');
|
|
139
|
+
expect(props.onChange).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should not paste numbers which doesn't start with the country code", () => {
|
|
143
|
+
simulatePaste(component.find('input'), '0+36303932551');
|
|
144
|
+
expect(select().props().value).toBe('+39');
|
|
145
|
+
expect(input().prop('value')).toBe('123456789');
|
|
146
|
+
expect(props.onChange).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should not paste numbers which doesn't contain a country code", () => {
|
|
150
|
+
simulatePaste(component.find('input'), '06303932551');
|
|
151
|
+
expect(select().props().value).toBe('+39');
|
|
152
|
+
expect(input().prop('value')).toBe('123456789');
|
|
153
|
+
expect(props.onChange).not.toHaveBeenCalled();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('when a model is supplied that could match more than one prefix', () => {
|
|
158
|
+
beforeEach(() => {
|
|
159
|
+
component = shallow(<PhoneNumberInput {...props} initialValue="+1868123456789" />);
|
|
160
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
161
|
+
input = component.find(NUMBER_SELECTOR);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should set the select to the longest matching prefix', () => {
|
|
165
|
+
expect(select.props().value).toBe('+1868');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should set the number input to the rest of the number', () => {
|
|
169
|
+
expect(input.prop('value')).toBe('123456789');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('when a model is supplied with no matching prefix', () => {
|
|
174
|
+
beforeEach(() => {
|
|
175
|
+
component = shallow(<PhoneNumberInput {...props} initialValue="+999123456789" />);
|
|
176
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
177
|
+
input = component.find(NUMBER_SELECTOR);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should empty the select', () => {
|
|
181
|
+
expect(select.props().value).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should put the whole value in the input without the plus', () => {
|
|
185
|
+
expect(input.prop('value')).toBe('999123456789');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('when an invalid model is supplied', () => {
|
|
190
|
+
it('should not re-explode model', () => {
|
|
191
|
+
component = shallow(<PhoneNumberInput {...props} initialValue="+123" />);
|
|
192
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
193
|
+
input = component.find(NUMBER_SELECTOR);
|
|
194
|
+
|
|
195
|
+
expect(select.props().value).toBe('+44');
|
|
196
|
+
expect(input.prop('value')).toBe('');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('when disabled is true', () => {
|
|
201
|
+
beforeEach(() => {
|
|
202
|
+
component = shallow(<PhoneNumberInput {...props} disabled />);
|
|
203
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
204
|
+
input = component.find(NUMBER_SELECTOR);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should disable the select', () => {
|
|
208
|
+
expect(select.prop('disabled')).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should disable the input', () => {
|
|
212
|
+
expect(input.prop('disabled')).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('when supplied with a placeholder', () => {
|
|
217
|
+
beforeEach(() => {
|
|
218
|
+
useIntl.mockReturnValue({ locale: 'es-ES', formatMessage });
|
|
219
|
+
component = shallow(
|
|
220
|
+
<PhoneNumberInput {...props} initialValue="+12345678" placeholder="placeholder" />,
|
|
221
|
+
);
|
|
222
|
+
input = component.find(NUMBER_SELECTOR);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should use the provided placeholder', () => {
|
|
226
|
+
expect(input.prop('placeholder')).toBe('placeholder');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('when supplied with a search placeholder', () => {
|
|
231
|
+
beforeEach(() => {
|
|
232
|
+
useIntl.mockReturnValue({ locale: 'es-ES', formatMessage });
|
|
233
|
+
component = shallow(
|
|
234
|
+
<PhoneNumberInput
|
|
235
|
+
{...props}
|
|
236
|
+
initialValue="+12345678"
|
|
237
|
+
searchPlaceholder="searchPlaceholder"
|
|
238
|
+
/>,
|
|
239
|
+
);
|
|
240
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should use the provided searchPlaceholder', () => {
|
|
244
|
+
expect(select.prop('filterPlaceholder')).toBe('searchPlaceholder');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('when supplied with a locale', () => {
|
|
249
|
+
describe('and a value', () => {
|
|
250
|
+
beforeEach(() => {
|
|
251
|
+
useIntl.mockReturnValue({ locale: 'es-ES', formatMessage });
|
|
252
|
+
component = shallow(<PhoneNumberInput {...props} initialValue="+12345678" />);
|
|
253
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
254
|
+
input = component.find(NUMBER_SELECTOR);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should use the prefix of the supplied value', () => {
|
|
258
|
+
expect(select.props().value).toBe('+1');
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('and no value', () => {
|
|
263
|
+
describe('and no country code', () => {
|
|
264
|
+
beforeEach(() => {
|
|
265
|
+
useIntl.mockReturnValue({ locale: 'es', formatMessage });
|
|
266
|
+
component = shallow(<PhoneNumberInput {...props} />);
|
|
267
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
268
|
+
input = component.find(NUMBER_SELECTOR);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should default the prefix to the local country', () => {
|
|
272
|
+
expect(select.props().value).toBe('+34');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('and country code', () => {
|
|
277
|
+
beforeEach(() => {
|
|
278
|
+
useIntl.mockReturnValue({ locale: 'es-ES', formatMessage });
|
|
279
|
+
component = shallow(<PhoneNumberInput {...props} countryCode="US" />);
|
|
280
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
281
|
+
input = component.find(NUMBER_SELECTOR);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should override locale prefix with country specific prefix', () => {
|
|
285
|
+
expect(select.props().value).toBe('+1');
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe('when user insert valid value', () => {
|
|
292
|
+
beforeEach(() => {
|
|
293
|
+
component = mount(<PhoneNumberInput {...props} />);
|
|
294
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
295
|
+
input = component.find(NUMBER_SELECTOR);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should trigger onChange handler', () => {
|
|
299
|
+
input.simulate('change', { target: { value: '123' } });
|
|
300
|
+
expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('when user insert an invalid number', () => {
|
|
305
|
+
it('should trigger onChange with null value', () => {
|
|
306
|
+
component = mount(<PhoneNumberInput {...props} initialValue="+12345678" />);
|
|
307
|
+
input = component.find(NUMBER_SELECTOR);
|
|
308
|
+
|
|
309
|
+
input.simulate('change', { target: { value: '1' } });
|
|
310
|
+
expect(props.onChange).toHaveBeenCalledWith(null, '+1');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("shouldn't re-trigger onChange and set previousReturned state", () => {
|
|
314
|
+
component = shallow(<PhoneNumberInput {...props} />);
|
|
315
|
+
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
316
|
+
input = component.find(NUMBER_SELECTOR);
|
|
317
|
+
|
|
318
|
+
select.simulate('change', { value: { value: '+1', label: '+1' } });
|
|
319
|
+
input.simulate('change', { target: { value: '12' } });
|
|
320
|
+
expect(props.onChange).not.toHaveBeenCalled();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('when user insert invalid character', () => {
|
|
325
|
+
it('should strip them', () => {
|
|
326
|
+
component = mount(<PhoneNumberInput {...props} value="+12345678" />);
|
|
327
|
+
input = component.find(NUMBER_SELECTOR);
|
|
328
|
+
|
|
329
|
+
input.simulate('change', { target: { value: '123--' } });
|
|
330
|
+
expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('overlapping prefix and suffix numbers', () => {
|
|
335
|
+
it("shouldn't change the prefix number on matching suffix input", () => {
|
|
336
|
+
component = mount(<PhoneNumberInput {...props} countryCode="eg" />);
|
|
337
|
+
component.find(NUMBER_SELECTOR).simulate('change', { target: { value: '1111111' } });
|
|
338
|
+
|
|
339
|
+
expect(component.find(NUMBER_SELECTOR).props().value).toBe('1111111');
|
|
340
|
+
expect(props.onChange).toHaveBeenCalledWith('+201111111', '+20');
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('when selectProps is supplied', () => {
|
|
345
|
+
beforeEach(() => {
|
|
346
|
+
component = shallow(
|
|
347
|
+
<PhoneNumberInput selectProps={{ className: 'custom-class' }} {...props} />,
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('renders Select component with expected props', () => {
|
|
352
|
+
const select = component.find(PREFIX_SELECT_SELECTOR);
|
|
353
|
+
expect(select.prop('className')).toBe('custom-class');
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
import { Field } from '../field/Field';
|
|
2
|
-
import {
|
|
3
|
-
mockMatchMedia,
|
|
4
|
-
mockResizeObserver,
|
|
5
|
-
render,
|
|
6
|
-
screen,
|
|
7
|
-
within,
|
|
8
|
-
userEvent,
|
|
9
|
-
} from '../test-utils';
|
|
10
|
-
|
|
11
|
-
import PhoneNumberInput, { PhoneNumberInputProps } from './PhoneNumberInput';
|
|
12
|
-
import { useIntl } from 'react-intl';
|
|
13
|
-
|
|
14
|
-
const formatMessage = (message: { id?: string }) => String(message.id);
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
16
|
-
jest.mock('react-intl', () => ({
|
|
17
|
-
...jest.requireActual('react-intl'),
|
|
18
|
-
useIntl: jest.fn(() => ({ formatMessage, locale: 'en-GB' })),
|
|
19
|
-
}));
|
|
20
|
-
const mockUseIntl = useIntl as jest.Mock;
|
|
21
|
-
|
|
22
|
-
mockMatchMedia();
|
|
23
|
-
mockResizeObserver();
|
|
24
|
-
|
|
25
|
-
describe('PhoneNumberInput', () => {
|
|
26
|
-
afterEach(jest.clearAllMocks);
|
|
27
|
-
|
|
28
|
-
const props = { onChange: jest.fn() };
|
|
29
|
-
|
|
30
|
-
const customRender = (overrides: Partial<PhoneNumberInputProps> = {}) =>
|
|
31
|
-
render(<PhoneNumberInput {...props} {...overrides} />);
|
|
32
|
-
|
|
33
|
-
const getPrefixEl = () => screen.getByRole('combobox');
|
|
34
|
-
const getInputEl = () => screen.getByRole('textbox');
|
|
35
|
-
|
|
36
|
-
describe('defaults', () => {
|
|
37
|
-
it('should set prefix control to default UK value', () => {
|
|
38
|
-
customRender();
|
|
39
|
-
expect(getPrefixEl()).toHaveTextContent('+44');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should set number control to empty', () => {
|
|
43
|
-
customRender();
|
|
44
|
-
expect(getInputEl()).toHaveValue('');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should not disable the select', () => {
|
|
48
|
-
customRender();
|
|
49
|
-
expect(getPrefixEl()).toBeEnabled();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should not disable the input', () => {
|
|
53
|
-
customRender();
|
|
54
|
-
expect(getInputEl()).toBeEnabled();
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should propagate `initialValue`', () => {
|
|
59
|
-
const prefix = '+39';
|
|
60
|
-
const number = '123456789';
|
|
61
|
-
customRender({ initialValue: `${prefix}${number}` });
|
|
62
|
-
expect(getPrefixEl()).toHaveTextContent(prefix);
|
|
63
|
-
expect(getInputEl()).toHaveValue(number);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('id prop', () => {
|
|
67
|
-
it('should not render id by default', () => {
|
|
68
|
-
customRender();
|
|
69
|
-
expect(getPrefixEl()).not.toHaveAttribute('id');
|
|
70
|
-
expect(getInputEl()).not.toHaveAttribute('id');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should respect `id` for the input and ignore the select', () => {
|
|
74
|
-
const id = 'component-id';
|
|
75
|
-
customRender({ id });
|
|
76
|
-
expect(getPrefixEl()).not.toHaveAttribute('id');
|
|
77
|
-
expect(getInputEl()).toHaveAttribute('id', id);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('pasting', () => {
|
|
82
|
-
const initialPrefix = '+48';
|
|
83
|
-
const initialNumber = '987654321';
|
|
84
|
-
|
|
85
|
-
const renderAndPaste = async (value: string) => {
|
|
86
|
-
customRender({ initialValue: `${initialPrefix}${initialNumber}` });
|
|
87
|
-
await userEvent.tab();
|
|
88
|
-
await userEvent.tab();
|
|
89
|
-
await userEvent.paste(value);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
[
|
|
93
|
-
{ number: '+36303932551', countryCode: '+36', localNumber: '303932551' },
|
|
94
|
-
{ number: '+39123456781', countryCode: '+39', localNumber: '123456781' },
|
|
95
|
-
{ number: '+44 7700 900415', countryCode: '+44', localNumber: '7700900415' },
|
|
96
|
-
{ number: '+2975557308515', countryCode: '+297', localNumber: '5557308515' },
|
|
97
|
-
{ number: '+297-555-7217-360', countryCode: '+297', localNumber: '5557217360' },
|
|
98
|
-
{ number: '+213-555-5160-67', countryCode: '+213', localNumber: '555516067' },
|
|
99
|
-
{ number: '+246-387-5553', countryCode: '+246', localNumber: '3875553' },
|
|
100
|
-
{ number: '+852-940-5558--6', countryCode: '+852', localNumber: '94055586' },
|
|
101
|
-
{ number: '+228 253 5558 4', countryCode: '+228', localNumber: '25355584' },
|
|
102
|
-
].forEach(({ number, countryCode, localNumber }) => {
|
|
103
|
-
it(`'${number}' number should update the value properly`, async () => {
|
|
104
|
-
await renderAndPaste(number);
|
|
105
|
-
|
|
106
|
-
expect(getPrefixEl()).toHaveTextContent(countryCode);
|
|
107
|
-
expect(getInputEl()).toHaveValue(localNumber);
|
|
108
|
-
expect(props.onChange).toHaveBeenCalledWith(number.replace(/[\s-]+/g, ''), countryCode);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should not paste invalid characters', async () => {
|
|
113
|
-
await renderAndPaste('+36asdasdasd');
|
|
114
|
-
expect(getPrefixEl()).toHaveTextContent(initialPrefix);
|
|
115
|
-
expect(getInputEl()).toHaveValue(initialNumber);
|
|
116
|
-
expect(props.onChange).not.toHaveBeenCalled();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should not paste countries which are not in the select', async () => {
|
|
120
|
-
await renderAndPaste('+9992342343423');
|
|
121
|
-
expect(getPrefixEl()).toHaveTextContent(initialPrefix);
|
|
122
|
-
expect(getInputEl()).toHaveValue(initialNumber);
|
|
123
|
-
expect(props.onChange).not.toHaveBeenCalled();
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("should not paste numbers which doesn't start with the country code", async () => {
|
|
127
|
-
await renderAndPaste('0+36303932551');
|
|
128
|
-
expect(getPrefixEl()).toHaveTextContent(initialPrefix);
|
|
129
|
-
expect(getInputEl()).toHaveValue(initialNumber);
|
|
130
|
-
expect(props.onChange).not.toHaveBeenCalled();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("should allow pasting numbers which don't contain a country code", async () => {
|
|
134
|
-
const newNumber = '06303932551';
|
|
135
|
-
await renderAndPaste(newNumber);
|
|
136
|
-
expect(getPrefixEl()).toHaveTextContent(initialPrefix);
|
|
137
|
-
expect(getInputEl()).toHaveValue(newNumber);
|
|
138
|
-
expect(props.onChange).toHaveBeenCalledWith(`${initialPrefix}${newNumber}`, initialPrefix);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe('initialValue', () => {
|
|
143
|
-
describe('when a model is supplied that could match more than one prefix', () => {
|
|
144
|
-
const initialProps = { initialValue: '+1868123456789' };
|
|
145
|
-
|
|
146
|
-
it('should set the select to the longest matching prefix', () => {
|
|
147
|
-
customRender(initialProps);
|
|
148
|
-
expect(getPrefixEl()).toHaveTextContent('+1868');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should set the number input to the rest of the number', () => {
|
|
152
|
-
customRender(initialProps);
|
|
153
|
-
expect(getInputEl()).toHaveValue('123456789');
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe('when a model is supplied with no matching prefix', () => {
|
|
158
|
-
const initialProps = { initialValue: '+999123456789' };
|
|
159
|
-
|
|
160
|
-
it('should empty the select', () => {
|
|
161
|
-
customRender(initialProps);
|
|
162
|
-
expect(getPrefixEl()).toHaveTextContent('neptune.PhoneNumberInput.SelectInput.placeholder');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should put the whole value in the input without the plus', () => {
|
|
166
|
-
customRender(initialProps);
|
|
167
|
-
expect(getInputEl()).toHaveValue('999123456789');
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe('when an invalid model is supplied', () => {
|
|
172
|
-
it('should not re-explode model', () => {
|
|
173
|
-
customRender({ initialValue: '+123' });
|
|
174
|
-
expect(getPrefixEl()).toHaveTextContent('+44');
|
|
175
|
-
expect(getInputEl()).toHaveValue('');
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('when disabled is true', () => {
|
|
181
|
-
it('should disable both controls', () => {
|
|
182
|
-
customRender({ disabled: true });
|
|
183
|
-
expect(getPrefixEl()).toBeDisabled();
|
|
184
|
-
expect(getInputEl()).toBeDisabled();
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('placeholders', () => {
|
|
189
|
-
it('should use the provided placeholder', () => {
|
|
190
|
-
const placeholder = 'custom placeholder';
|
|
191
|
-
customRender({ placeholder });
|
|
192
|
-
expect(getInputEl()).toHaveAttribute('placeholder', placeholder);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('should use the provided searchPlaceholder', async () => {
|
|
196
|
-
const searchPlaceholder = 'search placeholder';
|
|
197
|
-
customRender({ searchPlaceholder });
|
|
198
|
-
await userEvent.click(getPrefixEl());
|
|
199
|
-
expect(screen.getByRole('combobox', { name: searchPlaceholder })).toBeInTheDocument();
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
describe('when supplied with a locale', () => {
|
|
204
|
-
beforeEach(() => {
|
|
205
|
-
mockUseIntl.mockReturnValue({ locale: 'es-ES', formatMessage });
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
afterEach(() => {
|
|
209
|
-
mockUseIntl.mockReturnValue({ locale: 'en-GB', formatMessage });
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('and a value', () => {
|
|
213
|
-
it('should use the prefix of the supplied value', () => {
|
|
214
|
-
customRender({ initialValue: '+12345678' });
|
|
215
|
-
expect(getPrefixEl()).toHaveTextContent('+1');
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe('and no value', () => {
|
|
220
|
-
describe('and no country code', () => {
|
|
221
|
-
it('should default the prefix to the local country', () => {
|
|
222
|
-
customRender();
|
|
223
|
-
expect(getPrefixEl()).toHaveTextContent('+34');
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe('and country code', () => {
|
|
228
|
-
it('should override locale prefix with country specific prefix', () => {
|
|
229
|
-
customRender({ countryCode: 'US' });
|
|
230
|
-
expect(getPrefixEl()).toHaveTextContent('+1');
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('user input', () => {
|
|
237
|
-
describe('valid number', () => {
|
|
238
|
-
it('should trigger onChange handler', async () => {
|
|
239
|
-
customRender();
|
|
240
|
-
await userEvent.type(getInputEl(), '123');
|
|
241
|
-
expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
describe('invalid number', () => {
|
|
246
|
-
it('should trigger onChange with null value', async () => {
|
|
247
|
-
customRender({ initialValue: '+1234' });
|
|
248
|
-
await userEvent.type(getInputEl(), '{Backspace}{Backspace}{Backspace}1');
|
|
249
|
-
expect(props.onChange).toHaveBeenCalledWith(null, '+1');
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
describe('when user insert invalid character', () => {
|
|
254
|
-
it('should strip them', async () => {
|
|
255
|
-
customRender({ initialValue: '+12345678' });
|
|
256
|
-
await userEvent.type(getInputEl(), '123--');
|
|
257
|
-
expect(props.onChange).toHaveBeenCalledWith('+12345678123', '+1');
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
describe('overlapping prefix and suffix numbers', () => {
|
|
262
|
-
it("shouldn't change the prefix number on matching suffix input", async () => {
|
|
263
|
-
customRender({ countryCode: 'eg' });
|
|
264
|
-
await userEvent.type(getInputEl(), '1111111');
|
|
265
|
-
expect(getInputEl()).toHaveValue('1111111');
|
|
266
|
-
expect(props.onChange).toHaveBeenCalledWith('+201111111', '+20');
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
describe('when selectProps is supplied', () => {
|
|
272
|
-
it('renders Select component with expected props', () => {
|
|
273
|
-
customRender({ selectProps: { className: 'custom-class' } });
|
|
274
|
-
expect(getPrefixEl().parentElement).toHaveClass('custom-class');
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('supports custom `aria-labelledby` attribute', () => {
|
|
279
|
-
render(
|
|
280
|
-
<>
|
|
281
|
-
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
|
282
|
-
<label id="prioritized-label">Prioritized label</label>
|
|
283
|
-
<PhoneNumberInput aria-labelledby="prioritized-label" onChange={() => {}} />
|
|
284
|
-
</>,
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
expect(
|
|
288
|
-
within(screen.getByLabelText('Prioritized label')).getByRole('textbox'),
|
|
289
|
-
).toBeInTheDocument();
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('supports `Field` for labeling', () => {
|
|
293
|
-
render(
|
|
294
|
-
<Field label="Phone number">
|
|
295
|
-
<PhoneNumberInput initialValue="+12345678" onChange={() => {}} />
|
|
296
|
-
</Field>,
|
|
297
|
-
);
|
|
298
|
-
expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Phone number/);
|
|
299
|
-
});
|
|
300
|
-
});
|