@servicetitan/form 38.6.0 → 38.6.1
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/dist/phone-number-input/__tests__/phone-number-input-a2.test.d.ts +2 -0
- package/dist/phone-number-input/__tests__/phone-number-input-a2.test.d.ts.map +1 -0
- package/dist/phone-number-input/phone-number-input-a2.js +4 -4
- package/dist/phone-number-input/phone-number-input-a2.js.map +1 -1
- package/package.json +10 -8
- package/src/phone-number-input/__tests__/phone-number-input-a2.test.tsx +305 -0
- package/src/phone-number-input/phone-number-input-a2.tsx +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone-number-input-a2.test.d.ts","sourceRoot":"","sources":["../../../src/phone-number-input/__tests__/phone-number-input-a2.test.tsx"],"names":[],"mappings":"AACA,OAAO,2BAA2B,CAAC"}
|
|
@@ -36,14 +36,14 @@ export const PhoneNumberInputA2 = (props)=>{
|
|
|
36
36
|
if (sip) {
|
|
37
37
|
return /*#__PURE__*/ _jsx(TextField, {
|
|
38
38
|
...restProps,
|
|
39
|
-
value:
|
|
40
|
-
onChange:
|
|
39
|
+
value: value,
|
|
40
|
+
onChange: onChange,
|
|
41
41
|
onFocus: onFocus,
|
|
42
42
|
onBlur: onBlur,
|
|
43
43
|
disabled: disabled,
|
|
44
44
|
readOnly: readOnly,
|
|
45
|
-
type: "
|
|
46
|
-
placeholder:
|
|
45
|
+
type: "text",
|
|
46
|
+
placeholder: propPlaceholder
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
const renderTextField = (inputProps)=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/phone-number-input/phone-number-input-a2.tsx"],"sourcesContent":["import { ChangeEvent, FC, useCallback } from 'react';\nimport ReactInputMask from 'react-input-mask';\n\nimport { TextField, TextFieldProps } from '@servicetitan/anvil2';\nimport { Culture, CULTURE_TOKEN } from '@servicetitan/culture';\nimport { useOptionalDependencies } from '@servicetitan/react-ioc';\n\ntype PhoneNumberInputA2Props = TextFieldProps & {\n sip?: boolean;\n culture?: Culture;\n};\n\nexport const PhoneNumberInputA2: FC<PhoneNumberInputA2Props> = props => {\n const {\n value,\n onChange,\n placeholder: propPlaceholder,\n culture: propCulture,\n sip,\n onFocus,\n onBlur,\n disabled,\n readOnly,\n ...restProps\n } = props;\n\n const [injectedCulture] = useOptionalDependencies(CULTURE_TOKEN);\n const culture = propCulture ?? injectedCulture;\n const { SimplePhoneMask = '?9999999999', SimplePhonePlaceholder = '' } =\n culture?.PhoneFormat ?? {};\n\n const mask = SimplePhoneMask.replace(/\\?/g, '');\n const placeholder = propPlaceholder ?? SimplePhonePlaceholder;\n const displayValue = String(value ?? '').replace(/\\D/g, '');\n\n // Transform output value to digits-only (unmasked)\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n if (onChange) {\n const digitsOnly = event.target.value.replace(/\\D/g, '');\n\n const unmaskedEvent = {\n ...event,\n target: { ...event.target, value: digitsOnly },\n currentTarget: { ...event.currentTarget, value: digitsOnly },\n } as ChangeEvent<HTMLInputElement>;\n\n onChange(unmaskedEvent);\n }\n },\n [onChange]\n );\n\n if (sip) {\n return (\n <TextField\n {...restProps}\n value={
|
|
1
|
+
{"version":3,"sources":["../../src/phone-number-input/phone-number-input-a2.tsx"],"sourcesContent":["import { ChangeEvent, FC, useCallback } from 'react';\nimport ReactInputMask from 'react-input-mask';\n\nimport { TextField, TextFieldProps } from '@servicetitan/anvil2';\nimport { Culture, CULTURE_TOKEN } from '@servicetitan/culture';\nimport { useOptionalDependencies } from '@servicetitan/react-ioc';\n\ntype PhoneNumberInputA2Props = TextFieldProps & {\n sip?: boolean;\n culture?: Culture;\n};\n\nexport const PhoneNumberInputA2: FC<PhoneNumberInputA2Props> = props => {\n const {\n value,\n onChange,\n placeholder: propPlaceholder,\n culture: propCulture,\n sip,\n onFocus,\n onBlur,\n disabled,\n readOnly,\n ...restProps\n } = props;\n\n const [injectedCulture] = useOptionalDependencies(CULTURE_TOKEN);\n const culture = propCulture ?? injectedCulture;\n const { SimplePhoneMask = '?9999999999', SimplePhonePlaceholder = '' } =\n culture?.PhoneFormat ?? {};\n\n const mask = SimplePhoneMask.replace(/\\?/g, '');\n const placeholder = propPlaceholder ?? SimplePhonePlaceholder;\n const displayValue = String(value ?? '').replace(/\\D/g, '');\n\n // Transform output value to digits-only (unmasked)\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n if (onChange) {\n const digitsOnly = event.target.value.replace(/\\D/g, '');\n\n const unmaskedEvent = {\n ...event,\n target: { ...event.target, value: digitsOnly },\n currentTarget: { ...event.currentTarget, value: digitsOnly },\n } as ChangeEvent<HTMLInputElement>;\n\n onChange(unmaskedEvent);\n }\n },\n [onChange]\n );\n\n if (sip) {\n return (\n <TextField\n {...restProps}\n value={value}\n onChange={onChange}\n onFocus={onFocus}\n onBlur={onBlur}\n disabled={disabled}\n readOnly={readOnly}\n type=\"text\"\n placeholder={propPlaceholder}\n />\n );\n }\n\n const renderTextField = (inputProps: Record<string, unknown>) => {\n // Exclude 'size' as it conflicts with TextField's size prop type\n const { size: UNUSED_SIZE, ...filteredProps } = inputProps;\n return <TextField {...filteredProps} {...restProps} type=\"tel\" placeholder={placeholder} />;\n };\n\n return (\n <ReactInputMask\n value={displayValue}\n onChange={handleChange}\n mask={mask}\n maskChar={null}\n onFocus={onFocus}\n onBlur={onBlur}\n disabled={disabled}\n readOnly={readOnly}\n >\n {renderTextField as any}\n </ReactInputMask>\n );\n};\n"],"names":["useCallback","ReactInputMask","TextField","CULTURE_TOKEN","useOptionalDependencies","PhoneNumberInputA2","props","value","onChange","placeholder","propPlaceholder","culture","propCulture","sip","onFocus","onBlur","disabled","readOnly","restProps","injectedCulture","SimplePhoneMask","SimplePhonePlaceholder","PhoneFormat","mask","replace","displayValue","String","handleChange","event","digitsOnly","target","unmaskedEvent","currentTarget","type","renderTextField","inputProps","size","UNUSED_SIZE","filteredProps","maskChar"],"mappings":";AAAA,SAA0BA,WAAW,QAAQ,QAAQ;AACrD,OAAOC,oBAAoB,mBAAmB;AAE9C,SAASC,SAAS,QAAwB,uBAAuB;AACjE,SAAkBC,aAAa,QAAQ,wBAAwB;AAC/D,SAASC,uBAAuB,QAAQ,0BAA0B;AAOlE,OAAO,MAAMC,qBAAkDC,CAAAA;;IAC3D,MAAM,EACFC,KAAK,EACLC,QAAQ,EACRC,aAAaC,eAAe,EAC5BC,SAASC,WAAW,EACpBC,GAAG,EACHC,OAAO,EACPC,MAAM,EACNC,QAAQ,EACRC,QAAQ,EACR,GAAGC,WACN,GAAGZ;IAEJ,MAAM,CAACa,gBAAgB,GAAGf,wBAAwBD;IAClD,MAAMQ,UAAUC,wBAAAA,yBAAAA,cAAeO;IAC/B,MAAM,EAAEC,kBAAkB,aAAa,EAAEC,yBAAyB,EAAE,EAAE,WAClEV,oBAAAA,8BAAAA,QAASW,WAAW,uCAAI,CAAC;IAE7B,MAAMC,OAAOH,gBAAgBI,OAAO,CAAC,OAAO;IAC5C,MAAMf,cAAcC,4BAAAA,6BAAAA,kBAAmBW;IACvC,MAAMI,eAAeC,OAAOnB,kBAAAA,mBAAAA,QAAS,IAAIiB,OAAO,CAAC,OAAO;IAExD,mDAAmD;IACnD,MAAMG,eAAe3B,YACjB,CAAC4B;QACG,IAAIpB,UAAU;YACV,MAAMqB,aAAaD,MAAME,MAAM,CAACvB,KAAK,CAACiB,OAAO,CAAC,OAAO;YAErD,MAAMO,gBAAgB;gBAClB,GAAGH,KAAK;gBACRE,QAAQ;oBAAE,GAAGF,MAAME,MAAM;oBAAEvB,OAAOsB;gBAAW;gBAC7CG,eAAe;oBAAE,GAAGJ,MAAMI,aAAa;oBAAEzB,OAAOsB;gBAAW;YAC/D;YAEArB,SAASuB;QACb;IACJ,GACA;QAACvB;KAAS;IAGd,IAAIK,KAAK;QACL,qBACI,KAACX;YACI,GAAGgB,SAAS;YACbX,OAAOA;YACPC,UAAUA;YACVM,SAASA;YACTC,QAAQA;YACRC,UAAUA;YACVC,UAAUA;YACVgB,MAAK;YACLxB,aAAaC;;IAGzB;IAEA,MAAMwB,kBAAkB,CAACC;QACrB,iEAAiE;QACjE,MAAM,EAAEC,MAAMC,WAAW,EAAE,GAAGC,eAAe,GAAGH;QAChD,qBAAO,KAACjC;YAAW,GAAGoC,aAAa;YAAG,GAAGpB,SAAS;YAAEe,MAAK;YAAMxB,aAAaA;;IAChF;IAEA,qBACI,KAACR;QACGM,OAAOkB;QACPjB,UAAUmB;QACVJ,MAAMA;QACNgB,UAAU;QACVzB,SAASA;QACTC,QAAQA;QACRC,UAAUA;QACVC,UAAUA;kBAETiB;;AAGb,EAAE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/form",
|
|
3
|
-
"version": "38.6.
|
|
3
|
+
"version": "38.6.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"homepage": "https://docs.st.dev/docs/frontend/form",
|
|
6
6
|
"repository": {
|
|
@@ -18,14 +18,16 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@progress/kendo-react-dateinputs": "~5.5.0",
|
|
20
20
|
"@servicetitan/anvil2": "^2.0.1",
|
|
21
|
-
"@servicetitan/confirm": "^38.6.
|
|
22
|
-
"@servicetitan/culture": "^38.6.
|
|
21
|
+
"@servicetitan/confirm": "^38.6.1",
|
|
22
|
+
"@servicetitan/culture": "^38.6.1",
|
|
23
23
|
"@servicetitan/design-system": "~14.5.1",
|
|
24
|
-
"@servicetitan/form-state": "^38.6.
|
|
24
|
+
"@servicetitan/form-state": "^38.6.1",
|
|
25
25
|
"@servicetitan/hash-browser-router": "^34.2.0",
|
|
26
26
|
"@servicetitan/react-ioc": "^34.2.0",
|
|
27
27
|
"@servicetitan/tokens": ">=12.2.1",
|
|
28
28
|
"@servicetitan/web-components": "^34.2.0",
|
|
29
|
+
"@testing-library/jest-dom": "^6.4.2",
|
|
30
|
+
"@testing-library/react": "^14.2.1",
|
|
29
31
|
"@types/js-cookie": "^3.0.3",
|
|
30
32
|
"@types/react": "~18.2.55",
|
|
31
33
|
"@types/react-input-mask": "~2.0.5",
|
|
@@ -41,10 +43,10 @@
|
|
|
41
43
|
"peerDependencies": {
|
|
42
44
|
"@progress/kendo-react-dateinputs": "~5.5.0",
|
|
43
45
|
"@servicetitan/anvil2": ">=1.42.0",
|
|
44
|
-
"@servicetitan/confirm": "^38.6.
|
|
45
|
-
"@servicetitan/culture": "^38.6.
|
|
46
|
+
"@servicetitan/confirm": "^38.6.1",
|
|
47
|
+
"@servicetitan/culture": "^38.6.1",
|
|
46
48
|
"@servicetitan/design-system": ">=13.2.1",
|
|
47
|
-
"@servicetitan/form-state": "^38.6.
|
|
49
|
+
"@servicetitan/form-state": "^38.6.1",
|
|
48
50
|
"@servicetitan/react-ioc": ">21.0.0",
|
|
49
51
|
"@servicetitan/tokens": ">=12.2.1",
|
|
50
52
|
"accounting": "~0.4.1",
|
|
@@ -69,5 +71,5 @@
|
|
|
69
71
|
"less": true,
|
|
70
72
|
"webpack": false
|
|
71
73
|
},
|
|
72
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "fcd96d8dd56219340083a3c8a181c10a81da48ae"
|
|
73
75
|
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
|
|
4
|
+
import { Culture } from '@servicetitan/culture';
|
|
5
|
+
|
|
6
|
+
// Mock useOptionalDependencies from react-ioc
|
|
7
|
+
const mockUseOptionalDependencies = jest.fn();
|
|
8
|
+
jest.mock('@servicetitan/react-ioc', () => ({
|
|
9
|
+
...jest.requireActual('@servicetitan/react-ioc'),
|
|
10
|
+
useOptionalDependencies: (...args: any[]) => mockUseOptionalDependencies(...args),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// Mock TextField as a plain <input> to enable testing with testing-library
|
|
14
|
+
jest.mock('@servicetitan/anvil2', () => {
|
|
15
|
+
const { createElement, forwardRef } = jest.requireActual('react');
|
|
16
|
+
return {
|
|
17
|
+
TextField: forwardRef(
|
|
18
|
+
(
|
|
19
|
+
{
|
|
20
|
+
label,
|
|
21
|
+
error,
|
|
22
|
+
suffix,
|
|
23
|
+
prefix,
|
|
24
|
+
description,
|
|
25
|
+
hint,
|
|
26
|
+
loading,
|
|
27
|
+
moreInfo,
|
|
28
|
+
labelProps,
|
|
29
|
+
errorAriaLive,
|
|
30
|
+
maxLengthCounter,
|
|
31
|
+
...props
|
|
32
|
+
}: any,
|
|
33
|
+
ref: any
|
|
34
|
+
) => createElement('input', { ...props, ref, 'aria-label': label ?? 'phone' })
|
|
35
|
+
),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Mock react-input-mask: render children with mask-related props
|
|
40
|
+
jest.mock('react-input-mask', () => {
|
|
41
|
+
const { createElement } = jest.requireActual('react');
|
|
42
|
+
const mod: Record<string, unknown> = {
|
|
43
|
+
default: ({ children, mask, value, onChange, maskChar, ...rest }: any) => {
|
|
44
|
+
const inputProps = { value, onChange, 'data-mask': mask, ...rest };
|
|
45
|
+
if (typeof children === 'function') {
|
|
46
|
+
return children(inputProps);
|
|
47
|
+
}
|
|
48
|
+
return createElement('input', inputProps);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
52
|
+
mod.__esModule = true;
|
|
53
|
+
return mod;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
import { PhoneNumberInputA2 } from '../phone-number-input-a2';
|
|
57
|
+
|
|
58
|
+
const nanpCulture = {
|
|
59
|
+
PhoneFormat: {
|
|
60
|
+
SimplePhoneMask: '(999) 999-9999?',
|
|
61
|
+
SimplePhonePlaceholder: '(___) ___-____',
|
|
62
|
+
},
|
|
63
|
+
} as Culture;
|
|
64
|
+
|
|
65
|
+
const auCulture = {
|
|
66
|
+
PhoneFormat: {
|
|
67
|
+
SimplePhoneMask: '99 9999 9999',
|
|
68
|
+
SimplePhonePlaceholder: '__ ____ ____',
|
|
69
|
+
},
|
|
70
|
+
} as Culture;
|
|
71
|
+
|
|
72
|
+
describe('PhoneNumberInputA2', () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
jest.clearAllMocks();
|
|
75
|
+
mockUseOptionalDependencies.mockReturnValue([undefined]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('culture resolution', () => {
|
|
79
|
+
it('uses explicit culture prop over DI culture', () => {
|
|
80
|
+
mockUseOptionalDependencies.mockReturnValue([auCulture]);
|
|
81
|
+
|
|
82
|
+
render(<PhoneNumberInputA2 culture={nanpCulture} value="" onChange={jest.fn()} />);
|
|
83
|
+
|
|
84
|
+
const input = screen.getByRole('textbox');
|
|
85
|
+
expect(input).toHaveAttribute('placeholder', '(___) ___-____');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('uses DI culture when no culture prop is provided', () => {
|
|
89
|
+
mockUseOptionalDependencies.mockReturnValue([auCulture]);
|
|
90
|
+
|
|
91
|
+
render(<PhoneNumberInputA2 value="" onChange={jest.fn()} />);
|
|
92
|
+
|
|
93
|
+
const input = screen.getByRole('textbox');
|
|
94
|
+
expect(input).toHaveAttribute('placeholder', '__ ____ ____');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('uses fallback mask when no culture is available', () => {
|
|
98
|
+
mockUseOptionalDependencies.mockReturnValue([undefined]);
|
|
99
|
+
|
|
100
|
+
render(<PhoneNumberInputA2 value="" onChange={jest.fn()} />);
|
|
101
|
+
|
|
102
|
+
const input = screen.getByRole('textbox');
|
|
103
|
+
// Fallback mask is '?9999999999' → stripped to '9999999999'
|
|
104
|
+
expect(input).toHaveAttribute('data-mask', '9999999999');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('placeholder', () => {
|
|
109
|
+
it('uses explicit placeholder prop over culture placeholder', () => {
|
|
110
|
+
render(
|
|
111
|
+
<PhoneNumberInputA2
|
|
112
|
+
culture={nanpCulture}
|
|
113
|
+
placeholder="Enter phone"
|
|
114
|
+
value=""
|
|
115
|
+
onChange={jest.fn()}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const input = screen.getByRole('textbox');
|
|
120
|
+
expect(input).toHaveAttribute('placeholder', 'Enter phone');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('SIP mode', () => {
|
|
125
|
+
it('renders non-digit characters in value (no masking)', () => {
|
|
126
|
+
render(<PhoneNumberInputA2 sip value="sip:user@domain.com" onChange={jest.fn()} />);
|
|
127
|
+
|
|
128
|
+
const input = screen.getByRole('textbox');
|
|
129
|
+
// In SIP mode, value is passed through as-is (not stripped to digits)
|
|
130
|
+
expect(input).toHaveValue('sip:user@domain.com');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('passes onChange directly without digit stripping', () => {
|
|
134
|
+
const handleChange = jest.fn();
|
|
135
|
+
|
|
136
|
+
render(<PhoneNumberInputA2 sip value="" onChange={handleChange} />);
|
|
137
|
+
|
|
138
|
+
const input = screen.getByRole('textbox');
|
|
139
|
+
fireEvent.change(input, { target: { value: 'sip:test@host' } });
|
|
140
|
+
|
|
141
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
142
|
+
/*
|
|
143
|
+
* In SIP mode, onChange is the raw prop (not the digit-stripping wrapper).
|
|
144
|
+
* The event's target should be the actual DOM input element, not a
|
|
145
|
+
* plain object spread (which is what handleChange produces in non-SIP).
|
|
146
|
+
*/
|
|
147
|
+
const event = handleChange.mock.calls[0][0];
|
|
148
|
+
expect(event.target).toBe(input);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('renders input with type="text"', () => {
|
|
152
|
+
render(<PhoneNumberInputA2 sip value="" onChange={jest.fn()} />);
|
|
153
|
+
|
|
154
|
+
const input = screen.getByRole('textbox');
|
|
155
|
+
expect(input).toHaveAttribute('type', 'text');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('does not show culture placeholder', () => {
|
|
159
|
+
render(<PhoneNumberInputA2 sip culture={nanpCulture} value="" onChange={jest.fn()} />);
|
|
160
|
+
|
|
161
|
+
const input = screen.getByRole('textbox');
|
|
162
|
+
expect(input).not.toHaveAttribute('placeholder', '(___) ___-____');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('shows explicit placeholder even in SIP mode', () => {
|
|
166
|
+
render(
|
|
167
|
+
<PhoneNumberInputA2
|
|
168
|
+
sip
|
|
169
|
+
culture={nanpCulture}
|
|
170
|
+
placeholder="SIP address"
|
|
171
|
+
value=""
|
|
172
|
+
onChange={jest.fn()}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const input = screen.getByRole('textbox');
|
|
177
|
+
expect(input).toHaveAttribute('placeholder', 'SIP address');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('masked (non-SIP) mode', () => {
|
|
182
|
+
it('renders with ReactInputMask', () => {
|
|
183
|
+
render(<PhoneNumberInputA2 culture={nanpCulture} value="" onChange={jest.fn()} />);
|
|
184
|
+
|
|
185
|
+
const input = screen.getByRole('textbox');
|
|
186
|
+
// Our mock passes mask as data-mask attribute
|
|
187
|
+
expect(input).toHaveAttribute('data-mask', '(999) 999-9999');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('strips non-digits from onChange value', () => {
|
|
191
|
+
const handleChange = jest.fn();
|
|
192
|
+
|
|
193
|
+
render(<PhoneNumberInputA2 culture={nanpCulture} value="" onChange={handleChange} />);
|
|
194
|
+
|
|
195
|
+
const input = screen.getByRole('textbox');
|
|
196
|
+
fireEvent.change(input, {
|
|
197
|
+
target: { value: '(555) 123-4567' },
|
|
198
|
+
currentTarget: { value: '(555) 123-4567' },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
202
|
+
const event = handleChange.mock.calls[0][0];
|
|
203
|
+
expect(event.target.value).toBe('5551234567');
|
|
204
|
+
expect(event.currentTarget.value).toBe('5551234567');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('renders input with type="tel"', () => {
|
|
208
|
+
render(<PhoneNumberInputA2 culture={nanpCulture} value="" onChange={jest.fn()} />);
|
|
209
|
+
|
|
210
|
+
const input = screen.getByRole('textbox');
|
|
211
|
+
expect(input).toHaveAttribute('type', 'tel');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('displays digits-only value', () => {
|
|
215
|
+
render(
|
|
216
|
+
<PhoneNumberInputA2 culture={nanpCulture} value="5551234567" onChange={jest.fn()} />
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const input = screen.getByRole('textbox');
|
|
220
|
+
expect(input).toHaveValue('5551234567');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('strips question mark from mask', () => {
|
|
224
|
+
const cultureWithOptional = {
|
|
225
|
+
PhoneFormat: {
|
|
226
|
+
SimplePhoneMask: '(999) 999-9999?',
|
|
227
|
+
SimplePhonePlaceholder: '',
|
|
228
|
+
},
|
|
229
|
+
} as Culture;
|
|
230
|
+
|
|
231
|
+
render(
|
|
232
|
+
<PhoneNumberInputA2 culture={cultureWithOptional} value="" onChange={jest.fn()} />
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const input = screen.getByRole('textbox');
|
|
236
|
+
expect(input).toHaveAttribute('data-mask', '(999) 999-9999');
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('props forwarding', () => {
|
|
241
|
+
it('forwards disabled, readOnly, onFocus, onBlur in non-SIP mode', () => {
|
|
242
|
+
const onFocus = jest.fn();
|
|
243
|
+
const onBlur = jest.fn();
|
|
244
|
+
|
|
245
|
+
render(
|
|
246
|
+
<PhoneNumberInputA2
|
|
247
|
+
culture={nanpCulture}
|
|
248
|
+
value=""
|
|
249
|
+
onChange={jest.fn()}
|
|
250
|
+
disabled
|
|
251
|
+
readOnly
|
|
252
|
+
onFocus={onFocus}
|
|
253
|
+
onBlur={onBlur}
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const input = screen.getByRole('textbox');
|
|
258
|
+
expect(input).toBeDisabled();
|
|
259
|
+
expect(input).toHaveAttribute('readOnly');
|
|
260
|
+
|
|
261
|
+
fireEvent.focus(input);
|
|
262
|
+
expect(onFocus).toHaveBeenCalledTimes(1);
|
|
263
|
+
|
|
264
|
+
fireEvent.blur(input);
|
|
265
|
+
expect(onBlur).toHaveBeenCalledTimes(1);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('forwards disabled, readOnly, onFocus, onBlur in SIP mode', () => {
|
|
269
|
+
const onFocus = jest.fn();
|
|
270
|
+
const onBlur = jest.fn();
|
|
271
|
+
|
|
272
|
+
render(
|
|
273
|
+
<PhoneNumberInputA2
|
|
274
|
+
sip
|
|
275
|
+
value=""
|
|
276
|
+
onChange={jest.fn()}
|
|
277
|
+
disabled
|
|
278
|
+
readOnly
|
|
279
|
+
onFocus={onFocus}
|
|
280
|
+
onBlur={onBlur}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const input = screen.getByRole('textbox');
|
|
285
|
+
expect(input).toBeDisabled();
|
|
286
|
+
expect(input).toHaveAttribute('readOnly');
|
|
287
|
+
|
|
288
|
+
fireEvent.focus(input);
|
|
289
|
+
expect(onFocus).toHaveBeenCalledTimes(1);
|
|
290
|
+
|
|
291
|
+
fireEvent.blur(input);
|
|
292
|
+
expect(onBlur).toHaveBeenCalledTimes(1);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('does not call onChange when onChange is not provided', () => {
|
|
296
|
+
// Should not throw
|
|
297
|
+
render(<PhoneNumberInputA2 culture={nanpCulture} value="" />);
|
|
298
|
+
|
|
299
|
+
const input = screen.getByRole('textbox');
|
|
300
|
+
expect(() => {
|
|
301
|
+
fireEvent.change(input, { target: { value: '123' } });
|
|
302
|
+
}).not.toThrow();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
@@ -55,14 +55,14 @@ export const PhoneNumberInputA2: FC<PhoneNumberInputA2Props> = props => {
|
|
|
55
55
|
return (
|
|
56
56
|
<TextField
|
|
57
57
|
{...restProps}
|
|
58
|
-
value={
|
|
59
|
-
onChange={
|
|
58
|
+
value={value}
|
|
59
|
+
onChange={onChange}
|
|
60
60
|
onFocus={onFocus}
|
|
61
61
|
onBlur={onBlur}
|
|
62
62
|
disabled={disabled}
|
|
63
63
|
readOnly={readOnly}
|
|
64
|
-
type="
|
|
65
|
-
placeholder={
|
|
64
|
+
type="text"
|
|
65
|
+
placeholder={propPlaceholder}
|
|
66
66
|
/>
|
|
67
67
|
);
|
|
68
68
|
}
|