@transferwise/components 0.0.0-experimental-c46d48c → 0.0.0-experimental-5865548

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.
Files changed (55) hide show
  1. package/build/alert/Alert.js +8 -0
  2. package/build/alert/Alert.js.map +1 -1
  3. package/build/alert/Alert.mjs +8 -0
  4. package/build/alert/Alert.mjs.map +1 -1
  5. package/build/common/closeButton/CloseButton.js +3 -1
  6. package/build/common/closeButton/CloseButton.js.map +1 -1
  7. package/build/common/closeButton/CloseButton.mjs +3 -1
  8. package/build/common/closeButton/CloseButton.mjs.map +1 -1
  9. package/build/i18n/en.json +0 -2
  10. package/build/i18n/en.json.js +0 -2
  11. package/build/i18n/en.json.js.map +1 -1
  12. package/build/i18n/en.json.mjs +0 -2
  13. package/build/i18n/en.json.mjs.map +1 -1
  14. package/build/phoneNumberInput/PhoneNumberInput.js +2 -36
  15. package/build/phoneNumberInput/PhoneNumberInput.js.map +1 -1
  16. package/build/phoneNumberInput/PhoneNumberInput.messages.js +0 -6
  17. package/build/phoneNumberInput/PhoneNumberInput.messages.js.map +1 -1
  18. package/build/phoneNumberInput/PhoneNumberInput.messages.mjs +0 -6
  19. package/build/phoneNumberInput/PhoneNumberInput.messages.mjs.map +1 -1
  20. package/build/phoneNumberInput/PhoneNumberInput.mjs +2 -36
  21. package/build/phoneNumberInput/PhoneNumberInput.mjs.map +1 -1
  22. package/build/types/alert/Alert.d.ts.map +1 -1
  23. package/build/types/common/closeButton/CloseButton.d.ts +2 -0
  24. package/build/types/common/closeButton/CloseButton.d.ts.map +1 -1
  25. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  26. package/build/types/phoneNumberInput/PhoneNumberInput.messages.d.ts +0 -8
  27. package/build/types/phoneNumberInput/PhoneNumberInput.messages.d.ts.map +1 -1
  28. package/build/types/test-utils/index.d.ts +0 -4
  29. package/build/types/test-utils/index.d.ts.map +1 -1
  30. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts.map +1 -1
  31. package/build/withDisplayFormat/WithDisplayFormat.js +3 -2
  32. package/build/withDisplayFormat/WithDisplayFormat.js.map +1 -1
  33. package/build/withDisplayFormat/WithDisplayFormat.mjs +3 -2
  34. package/build/withDisplayFormat/WithDisplayFormat.mjs.map +1 -1
  35. package/package.json +4 -7
  36. package/src/alert/Alert.spec.tsx +11 -0
  37. package/src/alert/Alert.story.tsx +23 -9
  38. package/src/alert/Alert.tsx +14 -1
  39. package/src/common/closeButton/CloseButton.spec.tsx +13 -1
  40. package/src/common/closeButton/CloseButton.tsx +3 -0
  41. package/src/i18n/en.json +0 -2
  42. package/src/phoneNumberInput/PhoneNumberInput.messages.ts +0 -8
  43. package/src/phoneNumberInput/PhoneNumberInput.spec.tsx +43 -77
  44. package/src/phoneNumberInput/PhoneNumberInput.tsx +2 -34
  45. package/src/test-utils/jest.setup.ts +0 -4
  46. package/src/typeahead/Typeahead.spec.tsx +182 -0
  47. package/src/typeahead/typeaheadInput/TypeaheadInput.spec.tsx +103 -0
  48. package/src/typeahead/util/highlight.spec.tsx +43 -0
  49. package/src/withDisplayFormat/WithDisplayFormat.spec.js +11 -15
  50. package/src/withDisplayFormat/WithDisplayFormat.tsx +3 -2
  51. package/src/typeahead/Typeahead.rtl.spec.tsx +0 -54
  52. package/src/typeahead/Typeahead.spec.js +0 -404
  53. package/src/typeahead/typeaheadInput/TypeaheadInput.spec.js +0 -74
  54. package/src/typeahead/typeaheadOption/TypeaheadOption.spec.js +0 -75
  55. package/src/typeahead/util/highlight.spec.js +0 -34
@@ -15,6 +15,7 @@ export type CloseButtonProps = Pick<
15
15
  filled?: boolean;
16
16
  isDisabled?: boolean;
17
17
  testId?: string;
18
+ tabIndex?: number;
18
19
  };
19
20
 
20
21
  /**
@@ -29,6 +30,7 @@ export const CloseButton = forwardRef(function CloseButton(
29
30
  onClick,
30
31
  isDisabled,
31
32
  testId,
33
+ tabIndex,
32
34
  }: CloseButtonProps,
33
35
  reference: React.ForwardedRef<HTMLButtonElement | null>,
34
36
  ) {
@@ -55,6 +57,7 @@ export const CloseButton = forwardRef(function CloseButton(
55
57
  aria-disabled={isDisabled}
56
58
  disabled={isDisabled}
57
59
  data-testid={testId}
60
+ tabIndex={tabIndex}
58
61
  onClick={onClick}
59
62
  >
60
63
  <Icon size={size === Size.SMALL || size === Size.EXTRA_SMALL ? 16 : 24} />
package/src/i18n/en.json CHANGED
@@ -23,8 +23,6 @@
23
23
  "neptune.MoneyInput.Select.placeholder": "Select an option...",
24
24
  "neptune.MoneyInput.Select.selectCurrencyLabel": "Select currency",
25
25
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Select an option...",
26
- "neptune.PhoneNumberInput.countryCodeLabel": "Country code",
27
- "neptune.PhoneNumberInput.phoneNumberLabel": "Phone number",
28
26
  "neptune.Select.searchPlaceholder": "Search...",
29
27
  "neptune.SelectInput.noResultsFound": "No results found",
30
28
  "neptune.SelectOption.action.label": "Choose",
@@ -5,12 +5,4 @@ export default defineMessages({
5
5
  id: 'neptune.PhoneNumberInput.SelectInput.placeholder',
6
6
  defaultMessage: 'Select an option...',
7
7
  },
8
- countryCodeLabel: {
9
- id: 'neptune.PhoneNumberInput.countryCodeLabel',
10
- defaultMessage: 'Country code',
11
- },
12
- phoneNumberLabel: {
13
- id: 'neptune.PhoneNumberInput.phoneNumberLabel',
14
- defaultMessage: 'Phone number',
15
- },
16
8
  });
@@ -21,30 +21,28 @@ describe('PhoneNumberInput', () => {
21
21
  const customRender = (overrides: Partial<PhoneNumberInputProps> = {}, locale?: string) =>
22
22
  render(<PhoneNumberInput {...props} {...overrides} />, { locale });
23
23
 
24
- const getCountryCodeSelect = () => screen.getByRole('combobox');
25
- const getCountryCodeLabel = () => screen.getByText('Country code');
26
- const getPhoneNumberInput = () => screen.getByRole('textbox');
27
- const getPhoneNumberLabel = () => screen.getByText('Phone number');
24
+ const getPrefixEl = () => screen.getByRole('combobox');
25
+ const getInputEl = () => screen.getByRole('textbox');
28
26
 
29
27
  describe('defaults', () => {
30
28
  it('should set prefix control to default UK value', () => {
31
29
  customRender();
32
- expect(getCountryCodeSelect()).toHaveTextContent('+44');
30
+ expect(getPrefixEl()).toHaveTextContent('+44');
33
31
  });
34
32
 
35
33
  it('should set number control to empty', () => {
36
34
  customRender();
37
- expect(getPhoneNumberInput()).toHaveValue('');
35
+ expect(getInputEl()).toHaveValue('');
38
36
  });
39
37
 
40
38
  it('should not disable the select', () => {
41
39
  customRender();
42
- expect(getCountryCodeSelect()).toBeEnabled();
40
+ expect(getPrefixEl()).toBeEnabled();
43
41
  });
44
42
 
45
43
  it('should not disable the input', () => {
46
44
  customRender();
47
- expect(getPhoneNumberInput()).toBeEnabled();
45
+ expect(getInputEl()).toBeEnabled();
48
46
  });
49
47
  });
50
48
 
@@ -52,54 +50,22 @@ describe('PhoneNumberInput', () => {
52
50
  const prefix = '+39';
53
51
  const number = '123456789';
54
52
  customRender({ initialValue: `${prefix}${number}` });
55
- expect(getCountryCodeSelect()).toHaveTextContent(prefix);
56
- expect(getPhoneNumberInput()).toHaveValue(number);
53
+ expect(getPrefixEl()).toHaveTextContent(prefix);
54
+ expect(getInputEl()).toHaveValue(number);
57
55
  });
58
56
 
59
57
  describe('id prop', () => {
60
- it('should render sensible default IDs', () => {
58
+ it('should not render id by default', () => {
61
59
  customRender();
62
- const countryCodeSelectID = getCountryCodeSelect().getAttribute('id');
63
- expect(countryCodeSelectID).toMatch(/^country-code-select-[a-z0-9]{6}$/);
64
- const countryCodeLabelID = getCountryCodeLabel().getAttribute('id');
65
- expect(countryCodeLabelID).toMatch(/^country-code-label-[a-z0-9]{6}$/);
66
- const phoneNumberInputID = getPhoneNumberInput().getAttribute('id');
67
- expect(phoneNumberInputID).toMatch(/^phone-number-input-[a-z0-9]{6}$/);
68
- const phoneNumberLabelID = getPhoneNumberLabel().getAttribute('id');
69
- expect(phoneNumberLabelID).toMatch(/^phone-number-label-[a-z0-9]{6}$/);
60
+ expect(getPrefixEl()).not.toHaveAttribute('id');
61
+ expect(getInputEl()).not.toHaveAttribute('id');
70
62
  });
71
63
 
72
- it('should use the custom `id` as-is for the input but with slight modification for select', () => {
64
+ it('should respect `id` for the input and ignore the select', () => {
73
65
  const id = 'component-id';
74
66
  customRender({ id });
75
- expect(getPhoneNumberInput()).toHaveAttribute('id', id);
76
- expect(getPhoneNumberLabel()).toHaveAttribute('id', `${id}-phone-number-label`);
77
- expect(getCountryCodeSelect()).toHaveAttribute('id', `${id}-country-code-select`);
78
- expect(getCountryCodeLabel()).toHaveAttribute('id', `${id}-country-code-label`);
79
- });
80
-
81
- it('should set correct ARIA attributes on the country code select when id is provided', () => {
82
- const id = 'my-id';
83
- customRender({ id });
84
- expect(getCountryCodeSelect()).toHaveAttribute('aria-labelledby', `${id}-country-code-label`);
85
- });
86
-
87
- it('should set correct ARIA attributes on the country code select when id is not provided', () => {
88
- customRender();
89
- const selectLabelledBy = getCountryCodeSelect().getAttribute('aria-labelledby');
90
- expect(selectLabelledBy).toMatch(/^country-code-label-[a-z0-9]{6}$/);
91
- });
92
-
93
- it('should set correct ARIA attributes on the phone number input when id is provided', () => {
94
- const id = 'my-id';
95
- customRender({ id });
96
- expect(getPhoneNumberInput()).toHaveAttribute('aria-labelledby', `${id}-phone-number-label`);
97
- });
98
-
99
- it('should set correct ARIA attributes on the phone number input when id is not provided', () => {
100
- customRender();
101
- const inputLabelledBy = getPhoneNumberInput().getAttribute('aria-labelledby');
102
- expect(inputLabelledBy).toMatch(/^phone-number-label-[a-z0-9]{6}$/);
67
+ expect(getPrefixEl()).not.toHaveAttribute('id');
68
+ expect(getInputEl()).toHaveAttribute('id', id);
103
69
  });
104
70
  });
105
71
 
@@ -128,38 +94,38 @@ describe('PhoneNumberInput', () => {
128
94
  it(`'${number}' number should update the value properly`, async () => {
129
95
  await renderAndPaste(number);
130
96
 
131
- expect(getCountryCodeSelect()).toHaveTextContent(countryCode);
132
- expect(getPhoneNumberInput()).toHaveValue(localNumber);
97
+ expect(getPrefixEl()).toHaveTextContent(countryCode);
98
+ expect(getInputEl()).toHaveValue(localNumber);
133
99
  expect(props.onChange).toHaveBeenCalledWith(number.replace(/[\s-]+/g, ''), countryCode);
134
100
  });
135
101
  });
136
102
 
137
103
  it('should not paste invalid characters', async () => {
138
104
  await renderAndPaste('+36asdasdasd');
139
- expect(getCountryCodeSelect()).toHaveTextContent(initialPrefix);
140
- expect(getPhoneNumberInput()).toHaveValue(initialNumber);
105
+ expect(getPrefixEl()).toHaveTextContent(initialPrefix);
106
+ expect(getInputEl()).toHaveValue(initialNumber);
141
107
  expect(props.onChange).not.toHaveBeenCalled();
142
108
  });
143
109
 
144
110
  it('should not paste countries which are not in the select', async () => {
145
111
  await renderAndPaste('+9992342343423');
146
- expect(getCountryCodeSelect()).toHaveTextContent(initialPrefix);
147
- expect(getPhoneNumberInput()).toHaveValue(initialNumber);
112
+ expect(getPrefixEl()).toHaveTextContent(initialPrefix);
113
+ expect(getInputEl()).toHaveValue(initialNumber);
148
114
  expect(props.onChange).not.toHaveBeenCalled();
149
115
  });
150
116
 
151
117
  it("should not paste numbers which doesn't start with the country code", async () => {
152
118
  await renderAndPaste('0+36303932551');
153
- expect(getCountryCodeSelect()).toHaveTextContent(initialPrefix);
154
- expect(getPhoneNumberInput()).toHaveValue(initialNumber);
119
+ expect(getPrefixEl()).toHaveTextContent(initialPrefix);
120
+ expect(getInputEl()).toHaveValue(initialNumber);
155
121
  expect(props.onChange).not.toHaveBeenCalled();
156
122
  });
157
123
 
158
124
  it("should allow pasting numbers which don't contain a country code", async () => {
159
125
  const newNumber = '06303932551';
160
126
  await renderAndPaste(newNumber);
161
- expect(getCountryCodeSelect()).toHaveTextContent(initialPrefix);
162
- expect(getPhoneNumberInput()).toHaveValue(newNumber);
127
+ expect(getPrefixEl()).toHaveTextContent(initialPrefix);
128
+ expect(getInputEl()).toHaveValue(newNumber);
163
129
  expect(props.onChange).toHaveBeenCalledWith(`${initialPrefix}${newNumber}`, initialPrefix);
164
130
  });
165
131
  });
@@ -170,12 +136,12 @@ describe('PhoneNumberInput', () => {
170
136
 
171
137
  it('should set the select to the longest matching prefix', () => {
172
138
  customRender(initialProps);
173
- expect(getCountryCodeSelect()).toHaveTextContent('+1868');
139
+ expect(getPrefixEl()).toHaveTextContent('+1868');
174
140
  });
175
141
 
176
142
  it('should set the number input to the rest of the number', () => {
177
143
  customRender(initialProps);
178
- expect(getPhoneNumberInput()).toHaveValue('123456789');
144
+ expect(getInputEl()).toHaveValue('123456789');
179
145
  });
180
146
  });
181
147
 
@@ -184,20 +150,20 @@ describe('PhoneNumberInput', () => {
184
150
 
185
151
  it('should empty the select', () => {
186
152
  customRender(initialProps);
187
- expect(getCountryCodeSelect()).toHaveTextContent('Select an option...');
153
+ expect(getPrefixEl()).toHaveTextContent('Select an option...');
188
154
  });
189
155
 
190
156
  it('should put the whole value in the input without the plus', () => {
191
157
  customRender(initialProps);
192
- expect(getPhoneNumberInput()).toHaveValue('999123456789');
158
+ expect(getInputEl()).toHaveValue('999123456789');
193
159
  });
194
160
  });
195
161
 
196
162
  describe('when an partial model is supplied (with a matching prefix)', () => {
197
163
  it('should set the select to the matching prefix and put the rest of the number in the suffix', () => {
198
164
  customRender({ initialValue: '+123' });
199
- expect(getCountryCodeSelect()).toHaveTextContent('+1');
200
- expect(getPhoneNumberInput()).toHaveValue('23');
165
+ expect(getPrefixEl()).toHaveTextContent('+1');
166
+ expect(getInputEl()).toHaveValue('23');
201
167
  });
202
168
  });
203
169
  });
@@ -205,8 +171,8 @@ describe('PhoneNumberInput', () => {
205
171
  describe('when disabled is true', () => {
206
172
  it('should disable both controls', () => {
207
173
  customRender({ disabled: true });
208
- expect(getCountryCodeSelect()).toBeDisabled();
209
- expect(getPhoneNumberInput()).toBeDisabled();
174
+ expect(getPrefixEl()).toBeDisabled();
175
+ expect(getInputEl()).toBeDisabled();
210
176
  });
211
177
  });
212
178
 
@@ -214,13 +180,13 @@ describe('PhoneNumberInput', () => {
214
180
  it('should use the provided placeholder', () => {
215
181
  const placeholder = 'custom placeholder';
216
182
  customRender({ placeholder });
217
- expect(getPhoneNumberInput()).toHaveAttribute('placeholder', placeholder);
183
+ expect(getInputEl()).toHaveAttribute('placeholder', placeholder);
218
184
  });
219
185
 
220
186
  it('should use the provided searchPlaceholder', async () => {
221
187
  const searchPlaceholder = 'search placeholder';
222
188
  customRender({ searchPlaceholder });
223
- await userEvent.click(getCountryCodeSelect());
189
+ await userEvent.click(getPrefixEl());
224
190
  expect(screen.getByRole('combobox', { name: searchPlaceholder })).toBeInTheDocument();
225
191
  });
226
192
  });
@@ -229,7 +195,7 @@ describe('PhoneNumberInput', () => {
229
195
  describe('and a value', () => {
230
196
  it('should use the prefix of the supplied value', () => {
231
197
  customRender({ initialValue: '+12345678' }, 'es');
232
- expect(getCountryCodeSelect()).toHaveTextContent('+1');
198
+ expect(getPrefixEl()).toHaveTextContent('+1');
233
199
  });
234
200
  });
235
201
 
@@ -237,14 +203,14 @@ describe('PhoneNumberInput', () => {
237
203
  describe('and no country code', () => {
238
204
  it('should default the prefix to the local country', () => {
239
205
  customRender(undefined, 'es');
240
- expect(getCountryCodeSelect()).toHaveTextContent('+34');
206
+ expect(getPrefixEl()).toHaveTextContent('+34');
241
207
  });
242
208
  });
243
209
 
244
210
  describe('and country code', () => {
245
211
  it('should override locale prefix with country specific prefix', () => {
246
212
  customRender({ countryCode: 'US' }, 'es');
247
- expect(getCountryCodeSelect()).toHaveTextContent('+1');
213
+ expect(getPrefixEl()).toHaveTextContent('+1');
248
214
  });
249
215
  });
250
216
  });
@@ -254,7 +220,7 @@ describe('PhoneNumberInput', () => {
254
220
  describe('valid number', () => {
255
221
  it('should trigger onChange handler', async () => {
256
222
  customRender();
257
- await userEvent.type(getPhoneNumberInput(), '123');
223
+ await userEvent.type(getInputEl(), '123');
258
224
  expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
259
225
  });
260
226
  });
@@ -262,7 +228,7 @@ describe('PhoneNumberInput', () => {
262
228
  describe('invalid number', () => {
263
229
  it('should trigger onChange with null value', async () => {
264
230
  customRender({ initialValue: '+1234' });
265
- await userEvent.type(getPhoneNumberInput(), '{Backspace}{Backspace}{Backspace}1');
231
+ await userEvent.type(getInputEl(), '{Backspace}{Backspace}{Backspace}1');
266
232
  expect(props.onChange).toHaveBeenCalledWith(null, '+1');
267
233
  });
268
234
  });
@@ -270,7 +236,7 @@ describe('PhoneNumberInput', () => {
270
236
  describe('when user insert invalid character', () => {
271
237
  it('should strip them', async () => {
272
238
  customRender({ initialValue: '+12345678' });
273
- await userEvent.type(getPhoneNumberInput(), '123--');
239
+ await userEvent.type(getInputEl(), '123--');
274
240
  expect(props.onChange).toHaveBeenCalledWith('+12345678123', '+1');
275
241
  });
276
242
  });
@@ -278,8 +244,8 @@ describe('PhoneNumberInput', () => {
278
244
  describe('overlapping prefix and suffix numbers', () => {
279
245
  it("shouldn't change the prefix number on matching suffix input", async () => {
280
246
  customRender({ countryCode: 'eg' });
281
- await userEvent.type(getPhoneNumberInput(), '1111111');
282
- expect(getPhoneNumberInput()).toHaveValue('1111111');
247
+ await userEvent.type(getInputEl(), '1111111');
248
+ expect(getInputEl()).toHaveValue('1111111');
283
249
  expect(props.onChange).toHaveBeenCalledWith('+201111111', '+20');
284
250
  });
285
251
  });
@@ -288,7 +254,7 @@ describe('PhoneNumberInput', () => {
288
254
  describe('when selectProps is supplied', () => {
289
255
  it('renders Select component with expected props', () => {
290
256
  customRender({ selectProps: { className: 'custom-class' } });
291
- expect(getCountryCodeSelect().parentElement).toHaveClass('custom-class');
257
+ expect(getPrefixEl().parentElement).toHaveClass('custom-class');
292
258
  });
293
259
  });
294
260
 
@@ -4,6 +4,7 @@ import { useIntl } from 'react-intl';
4
4
  import { Size, SizeLarge, SizeMedium, SizeSmall } from '../common';
5
5
  import { useInputAttributes } from '../inputs/contexts';
6
6
  import { SelectInput, SelectInputOptionContent, SelectInputProps } from '../inputs/SelectInput';
7
+
7
8
  import messages from './PhoneNumberInput.messages';
8
9
  import countries from './data/countries';
9
10
  import {
@@ -63,25 +64,6 @@ const PhoneNumberInput = ({
63
64
 
64
65
  const { locale, formatMessage } = useIntl();
65
66
 
66
- const createId = (customID: string | undefined, backup: string): string => {
67
- if (customID) {
68
- return customID + (backup ? `-${backup}` : '');
69
- }
70
- const random = Math.random().toString(36).slice(2, 8);
71
- return `${backup}-${random}`;
72
- };
73
-
74
- const ids = {
75
- countryCode: {
76
- label: createId(id, 'country-code-label'),
77
- select: createId(id, 'country-code-select'),
78
- },
79
- phoneNumber: {
80
- label: createId(id, 'phone-number-label'),
81
- input: createId(id, id ? '' : 'phone-number-input'),
82
- },
83
- };
84
-
85
67
  const [internalValue, setInternalValue] = useState<PhoneNumber>(() => {
86
68
  const cleanValue = initialValue ? cleanNumber(initialValue) : null;
87
69
 
@@ -169,9 +151,6 @@ const PhoneNumberInput = ({
169
151
  aria-labelledby={ariaLabelledBy}
170
152
  className="tw-telephone"
171
153
  >
172
- <label className="sr-only" id={ids.countryCode.label}>
173
- {formatMessage(messages.countryCodeLabel)}
174
- </label>
175
154
  <div className="tw-telephone__country-select">
176
155
  <SelectInput
177
156
  placeholder={formatMessage(messages.selectInputPlaceholder)}
@@ -202,13 +181,6 @@ const PhoneNumberInput = ({
202
181
  filterPlaceholder={searchPlaceholder}
203
182
  disabled={disabled}
204
183
  size={size}
205
- id={ids.countryCode.select}
206
- UNSAFE_triggerButtonProps={{
207
- id: ids.countryCode.select,
208
- 'aria-labelledby': ids.countryCode.label,
209
- 'aria-describedby': undefined,
210
- 'aria-invalid': undefined,
211
- }}
212
184
  onChange={(prefix) => {
213
185
  const country = prefix != null ? findCountryByPrefix(prefix) : null;
214
186
  setInternalValue((prev) => ({ ...prev, prefix, format: country?.phoneFormat }));
@@ -221,13 +193,10 @@ const PhoneNumberInput = ({
221
193
  {...selectProps}
222
194
  />
223
195
  </div>
224
- <label className="sr-only" id={ids.phoneNumber.label} htmlFor={ids.phoneNumber.input}>
225
- {formatMessage(messages.phoneNumberLabel)}
226
- </label>
227
196
  <div className="tw-telephone__number-input">
228
197
  <div className={`input-group input-group-${size}`}>
229
198
  <Input
230
- id={ids.phoneNumber.input}
199
+ id={id}
231
200
  autoComplete="tel-national"
232
201
  name="phoneNumber"
233
202
  inputMode="numeric"
@@ -235,7 +204,6 @@ const PhoneNumberInput = ({
235
204
  disabled={disabled}
236
205
  required={required}
237
206
  placeholder={placeholder}
238
- aria-labelledby={ids.phoneNumber.label}
239
207
  onChange={onSuffixChange}
240
208
  onPaste={onPaste}
241
209
  onFocus={onFocus}
@@ -1,14 +1,10 @@
1
1
  import util from 'node:util';
2
2
  import '@testing-library/jest-dom';
3
3
 
4
- import Adapter from '@cfaester/enzyme-adapter-react-18';
5
- import Enzyme from 'enzyme';
6
4
  import fetchMock from 'jest-fetch-mock';
7
5
 
8
6
  global.fetch = fetchMock as unknown as typeof global.fetch;
9
7
 
10
- Enzyme.configure({ adapter: new Adapter() });
11
-
12
8
  global.requestAnimationFrame = (callback: (time: number) => void): number => {
13
9
  callback(performance.now());
14
10
  return 0;
@@ -0,0 +1,182 @@
1
+ import { Field } from '../field/Field';
2
+ import { mockMatchMedia, render, screen, fireEvent } from '../test-utils';
3
+ import { wait } from '../test-utils/wait';
4
+ import Typeahead from './Typeahead';
5
+
6
+ mockMatchMedia();
7
+
8
+ describe('Typeahead', () => {
9
+ it('supports `Field` for labeling', () => {
10
+ render(
11
+ <Field id="test" label="Tags">
12
+ <Typeahead id="test" name="test" options={[{ label: 'Test' }]} onChange={() => {}} />
13
+ </Field>,
14
+ );
15
+ expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Tags/);
16
+ });
17
+
18
+ describe('when no options are provided', () => {
19
+ it('does not render a dropdown when no options and no footer are provided', () => {
20
+ render(
21
+ <Field id="test" label="Tags">
22
+ <Typeahead id="test" name="test" options={[]} onChange={() => {}} />
23
+ </Field>,
24
+ );
25
+ expect(screen.queryByRole('menu')).not.toBeInTheDocument();
26
+ });
27
+ it('does render a dropdown when only a footer is provided', () => {
28
+ render(
29
+ <Field id="test" label="Tags">
30
+ <Typeahead id="test" name="test" options={[]} footer={<p>hello</p>} onChange={() => {}} />
31
+ </Field>,
32
+ );
33
+ expect(screen.getByRole('menu')).toBeInTheDocument();
34
+ });
35
+ });
36
+
37
+ it('renders input with placeholder', () => {
38
+ render(
39
+ <Typeahead id="test" name="test" options={[]} placeholder="Type here" onChange={() => {}} />,
40
+ );
41
+ expect(screen.getByPlaceholderText('Type here')).toBeInTheDocument();
42
+ });
43
+
44
+ it('renders chips when multiple is true and selected has items', () => {
45
+ const initialValue = [{ label: 'Chip 1' }, { label: 'Chip 2' }];
46
+ render(
47
+ <Typeahead
48
+ id="test"
49
+ name="test"
50
+ options={[]}
51
+ multiple
52
+ initialValue={initialValue}
53
+ onChange={() => {}}
54
+ />,
55
+ );
56
+ expect(screen.getByText('Chip 1')).toBeInTheDocument();
57
+ expect(screen.getByText('Chip 2')).toBeInTheDocument();
58
+ });
59
+
60
+ it('calls onChange when selecting an option', () => {
61
+ const onChange = jest.fn();
62
+ render(
63
+ <Typeahead
64
+ id="test"
65
+ name="test"
66
+ options={[{ label: 'Option 1' }, { label: 'Option 2' }]}
67
+ minQueryLength={0}
68
+ onChange={onChange}
69
+ />,
70
+ );
71
+ const input = screen.getByRole('combobox');
72
+ fireEvent.change(input, { target: { value: 'Option 1' } });
73
+ fireEvent.click(screen.getByText('Option 1'));
74
+ expect(onChange).toHaveBeenCalled();
75
+ });
76
+
77
+ it('shows clear button when there is a value', () => {
78
+ render(
79
+ <Typeahead
80
+ id="test"
81
+ name="test"
82
+ options={[]}
83
+ initialValue={[{ label: 'Chip' }]}
84
+ onChange={() => {}}
85
+ />,
86
+ );
87
+ expect(screen.getByRole('button', { name: /clear/i })).toBeInTheDocument();
88
+ });
89
+
90
+ it('clears value when clear button is clicked', () => {
91
+ const onChange = jest.fn();
92
+ render(
93
+ <Typeahead
94
+ id="test"
95
+ name="test"
96
+ options={[]}
97
+ initialValue={[{ label: 'Chip' }]}
98
+ onChange={onChange}
99
+ />,
100
+ );
101
+ fireEvent.click(screen.getByRole('button', { name: /clear/i }));
102
+ expect(onChange).toHaveBeenCalledWith([]);
103
+ });
104
+
105
+ it('shows InlineAlert when alert prop is provided', () => {
106
+ render(
107
+ <Typeahead
108
+ id="test"
109
+ name="test"
110
+ options={[]}
111
+ alert={{ message: 'Something went wrong', type: 'error' }}
112
+ onChange={() => {}}
113
+ />,
114
+ );
115
+ expect(screen.getByText('Something went wrong')).toBeInTheDocument();
116
+ });
117
+
118
+ it('calls onFocus when input is focused', () => {
119
+ const onFocus = jest.fn();
120
+ render(<Typeahead id="test" name="test" options={[]} onChange={() => {}} onFocus={onFocus} />);
121
+ fireEvent.focus(screen.getByRole('combobox'));
122
+ expect(onFocus).toHaveBeenCalled();
123
+ });
124
+
125
+ it('calls onInputChange when input value changes', () => {
126
+ const onInputChange = jest.fn();
127
+ render(
128
+ <Typeahead
129
+ id="test"
130
+ name="test"
131
+ options={[]}
132
+ minQueryLength={0}
133
+ onChange={() => {}}
134
+ onInputChange={onInputChange}
135
+ />,
136
+ );
137
+ fireEvent.change(screen.getByRole('combobox'), { target: { value: 'abc' } });
138
+ expect(onInputChange).toHaveBeenCalledWith('abc');
139
+ });
140
+
141
+ it('calls onSearch when input value changes', async () => {
142
+ const onSearch = jest.fn();
143
+ render(
144
+ <Typeahead
145
+ id="test"
146
+ name="test"
147
+ options={[]}
148
+ minQueryLength={0}
149
+ onChange={() => {}}
150
+ onSearch={onSearch}
151
+ />,
152
+ );
153
+ fireEvent.change(screen.getByRole('combobox'), { target: { value: 'abc' } });
154
+ // wait for debounce
155
+ await wait(500);
156
+ expect(onSearch).toHaveBeenCalledWith('abc');
157
+ });
158
+
159
+ it('adds a new chip when allowNew and multiple are true and separator is pasted', () => {
160
+ const onChange = jest.fn();
161
+ render(
162
+ <Typeahead
163
+ id="test"
164
+ name="test"
165
+ options={[]}
166
+ allowNew
167
+ multiple
168
+ chipSeparators={[',']}
169
+ onChange={onChange}
170
+ />,
171
+ );
172
+ const input = screen.getByRole('combobox');
173
+ fireEvent.paste(input, {
174
+ clipboardData: {
175
+ getData: () => 'foo,bar',
176
+ },
177
+ });
178
+ expect(onChange).toHaveBeenCalledWith(
179
+ expect.arrayContaining([{ label: 'foo' }, { label: 'bar' }]),
180
+ );
181
+ });
182
+ });