@transferwise/components 46.85.0 → 46.86.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/build/avatarLayout/AvatarLayout.js +4 -3
- package/build/avatarLayout/AvatarLayout.js.map +1 -1
- package/build/avatarLayout/AvatarLayout.mjs +4 -3
- package/build/avatarLayout/AvatarLayout.mjs.map +1 -1
- package/build/avatarView/AvatarView.js +12 -1
- package/build/avatarView/AvatarView.js.map +1 -1
- package/build/avatarView/AvatarView.mjs +12 -1
- package/build/avatarView/AvatarView.mjs.map +1 -1
- package/build/circularButton/CircularButton.js +18 -21
- package/build/circularButton/CircularButton.js.map +1 -1
- package/build/circularButton/CircularButton.mjs +19 -22
- package/build/circularButton/CircularButton.mjs.map +1 -1
- package/build/definitionList/DefinitionList.js.map +1 -1
- package/build/definitionList/DefinitionList.mjs.map +1 -1
- package/build/i18n/de.json +1 -0
- package/build/i18n/de.json.js +1 -0
- package/build/i18n/de.json.js.map +1 -1
- package/build/i18n/de.json.mjs +1 -0
- package/build/i18n/de.json.mjs.map +1 -1
- package/build/i18n/es.json +1 -0
- package/build/i18n/es.json.js +1 -0
- package/build/i18n/es.json.js.map +1 -1
- package/build/i18n/es.json.mjs +1 -0
- package/build/i18n/es.json.mjs.map +1 -1
- package/build/i18n/fr.json +6 -5
- package/build/i18n/fr.json.js +6 -5
- package/build/i18n/fr.json.js.map +1 -1
- package/build/i18n/fr.json.mjs +6 -5
- package/build/i18n/fr.json.mjs.map +1 -1
- package/build/i18n/hu.json +1 -0
- package/build/i18n/hu.json.js +1 -0
- package/build/i18n/hu.json.js.map +1 -1
- package/build/i18n/hu.json.mjs +1 -0
- package/build/i18n/hu.json.mjs.map +1 -1
- package/build/i18n/id.json +1 -0
- package/build/i18n/id.json.js +1 -0
- package/build/i18n/id.json.js.map +1 -1
- package/build/i18n/id.json.mjs +1 -0
- package/build/i18n/id.json.mjs.map +1 -1
- package/build/i18n/it.json +1 -0
- package/build/i18n/it.json.js +1 -0
- package/build/i18n/it.json.js.map +1 -1
- package/build/i18n/it.json.mjs +1 -0
- package/build/i18n/it.json.mjs.map +1 -1
- package/build/i18n/pl.json +1 -0
- package/build/i18n/pl.json.js +1 -0
- package/build/i18n/pl.json.js.map +1 -1
- package/build/i18n/pl.json.mjs +1 -0
- package/build/i18n/pl.json.mjs.map +1 -1
- package/build/i18n/ro.json +1 -0
- package/build/i18n/ro.json.js +1 -0
- package/build/i18n/ro.json.js.map +1 -1
- package/build/i18n/ro.json.mjs +1 -0
- package/build/i18n/ro.json.mjs.map +1 -1
- package/build/i18n/th.json +1 -0
- package/build/i18n/th.json.js +1 -0
- package/build/i18n/th.json.js.map +1 -1
- package/build/i18n/th.json.mjs +1 -0
- package/build/i18n/th.json.mjs.map +1 -1
- package/build/i18n/tr.json +1 -0
- package/build/i18n/tr.json.js +1 -0
- package/build/i18n/tr.json.js.map +1 -1
- package/build/i18n/tr.json.mjs +1 -0
- package/build/i18n/tr.json.mjs.map +1 -1
- package/build/i18n/zh-CN.json +1 -0
- package/build/i18n/zh-CN.json.js +1 -0
- package/build/i18n/zh-CN.json.js.map +1 -1
- package/build/i18n/zh-CN.json.mjs +1 -0
- package/build/i18n/zh-CN.json.mjs.map +1 -1
- package/build/main.css +18 -159
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/styles/avatarLayout/AvatarLayout.css +1 -1
- package/build/styles/circularButton/CircularButton.css +17 -158
- package/build/styles/main.css +18 -159
- package/build/types/avatarLayout/AvatarLayout.d.ts.map +1 -1
- package/build/types/avatarView/AvatarView.d.ts.map +1 -1
- package/build/types/circularButton/CircularButton.d.ts +11 -4
- package/build/types/circularButton/CircularButton.d.ts.map +1 -1
- package/build/types/definitionList/DefinitionList.d.ts +1 -2
- package/build/types/definitionList/DefinitionList.d.ts.map +1 -1
- package/build/types/moneyInput/MoneyInput.d.ts +1 -1
- package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/avatar/Avatar.story.tsx +4 -1
- package/src/avatarLayout/AvatarLayout.css +1 -1
- package/src/avatarLayout/AvatarLayout.less +1 -1
- package/src/avatarLayout/AvatarLayout.story.tsx +2 -0
- package/src/avatarLayout/AvatarLayout.tsx +6 -4
- package/src/avatarView/AvatarView.tsx +15 -1
- package/src/avatarWrapper/AvatarWrapper.story.tsx +4 -0
- package/src/badge/Badge.story.tsx +4 -0
- package/src/circularButton/CircularButton.css +17 -158
- package/src/circularButton/CircularButton.less +22 -91
- package/src/circularButton/CircularButton.story.tsx +45 -24
- package/src/circularButton/CircularButton.tsx +38 -25
- package/src/dateInput/DateInput.spec.tsx +45 -26
- package/src/definitionList/DefinitionList.story.tsx +57 -57
- package/src/definitionList/DefinitionList.tsx +1 -1
- package/src/i18n/de.json +1 -0
- package/src/i18n/es.json +1 -0
- package/src/i18n/fr.json +6 -5
- package/src/i18n/hu.json +1 -0
- package/src/i18n/id.json +1 -0
- package/src/i18n/it.json +1 -0
- package/src/i18n/pl.json +1 -0
- package/src/i18n/ro.json +1 -0
- package/src/i18n/th.json +1 -0
- package/src/i18n/tr.json +1 -0
- package/src/i18n/zh-CN.json +1 -0
- package/src/iconButton/IconButton.story.tsx +6 -6
- package/src/main.css +18 -159
- package/src/moneyInput/MoneyInput.spec.tsx +468 -0
- package/src/moneyInput/MoneyInput.tsx +2 -1
- package/src/phoneNumberInput/PhoneNumberInput.spec.tsx +283 -0
- package/src/slidingPanel/SlidingPanel.spec.tsx +69 -0
- package/src/circularButton/_button-label-states.less +0 -34
- package/src/definitionList/DefinitionList.spec.js +0 -91
- package/src/moneyInput/MoneyInput.rtl.spec.tsx +0 -149
- package/src/moneyInput/MoneyInput.spec.js +0 -820
- package/src/phoneNumberInput/PhoneNumberInput.rtl.spec.tsx +0 -32
- package/src/phoneNumberInput/PhoneNumberInput.spec.js +0 -356
- package/src/slidingPanel/SlidingPanel.spec.js +0 -56
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { mockMatchMedia, mockResizeObserver, render, screen, userEvent } from '../test-utils';
|
|
2
|
+
|
|
3
|
+
import { MoneyInput, CurrencyItem, CurrencyOptionItem, Field } from '..';
|
|
4
|
+
import { MoneyInputPropsWithInputAttributes } from './MoneyInput';
|
|
5
|
+
import { within } from '@testing-library/react';
|
|
6
|
+
import messages from './MoneyInput.messages';
|
|
7
|
+
|
|
8
|
+
mockMatchMedia();
|
|
9
|
+
mockResizeObserver();
|
|
10
|
+
|
|
11
|
+
describe('Money Input', () => {
|
|
12
|
+
const popularCurrencies: CurrencyOptionItem[] = [
|
|
13
|
+
{
|
|
14
|
+
value: 'EUR',
|
|
15
|
+
label: 'EUR',
|
|
16
|
+
note: 'Euro',
|
|
17
|
+
currency: 'eur',
|
|
18
|
+
searchable: 'Spain, Germany, France, Austria, Estonia',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: 'USD',
|
|
22
|
+
label: 'USD',
|
|
23
|
+
note: 'United States dollar',
|
|
24
|
+
currency: 'usd',
|
|
25
|
+
searchable: 'Hong Kong, Saudi Arabia',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
value: 'GBP',
|
|
29
|
+
label: 'GBP',
|
|
30
|
+
note: 'British pound',
|
|
31
|
+
currency: 'gbp',
|
|
32
|
+
searchable: 'England, Scotland, Wales',
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
const otherCurrencies: CurrencyOptionItem[] = [
|
|
36
|
+
{
|
|
37
|
+
value: 'CAD',
|
|
38
|
+
label: 'CAD',
|
|
39
|
+
note: 'Canadian dollar',
|
|
40
|
+
currency: 'cad',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
value: 'AUD',
|
|
44
|
+
label: 'AUD',
|
|
45
|
+
note: 'Australian dollar',
|
|
46
|
+
currency: 'aud',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
const currencies: CurrencyItem[] = [
|
|
50
|
+
{ header: 'Popular currencies' },
|
|
51
|
+
...popularCurrencies,
|
|
52
|
+
{ header: 'Some other currencies' },
|
|
53
|
+
...otherCurrencies,
|
|
54
|
+
];
|
|
55
|
+
const initialProps = {
|
|
56
|
+
currencies,
|
|
57
|
+
selectedCurrency: popularCurrencies[1],
|
|
58
|
+
amount: 1000,
|
|
59
|
+
onAmountChange: jest.fn(),
|
|
60
|
+
onCurrencyChange: jest.fn(),
|
|
61
|
+
onSearchChange: jest.fn(),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const customRender = (
|
|
65
|
+
overrides: Partial<MoneyInputPropsWithInputAttributes> = {},
|
|
66
|
+
locale?: string,
|
|
67
|
+
) => render(<MoneyInput {...initialProps} {...overrides} />, { locale });
|
|
68
|
+
|
|
69
|
+
beforeEach(jest.clearAllMocks);
|
|
70
|
+
|
|
71
|
+
const getTrigger = () => screen.getByRole('combobox', { name: /Select currency/ });
|
|
72
|
+
const getInput = () => screen.getAllByRole('textbox')[0];
|
|
73
|
+
|
|
74
|
+
const openDropdown = async () => {
|
|
75
|
+
await userEvent.click(getTrigger());
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getPopularGroup = () => screen.getByRole('group', { name: 'Popular currencies' });
|
|
79
|
+
const getPopularCurrencies = () => within(getPopularGroup()).getAllByRole('option');
|
|
80
|
+
const getOtherGroup = () => screen.getByRole('group', { name: 'Some other currencies' });
|
|
81
|
+
const getOtherCurrencies = () => within(getOtherGroup()).getAllByRole('option');
|
|
82
|
+
|
|
83
|
+
it('renders a select with all currencies grouped', async () => {
|
|
84
|
+
customRender();
|
|
85
|
+
await openDropdown();
|
|
86
|
+
expect(getPopularGroup()).toBeInTheDocument();
|
|
87
|
+
getPopularCurrencies().forEach((option, index) => {
|
|
88
|
+
expect(option).toHaveTextContent(
|
|
89
|
+
new RegExp(`${popularCurrencies[index].label}\\s*?${popularCurrencies[index].note}`),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
expect(getOtherGroup()).toBeInTheDocument();
|
|
93
|
+
getOtherCurrencies().forEach((option, index) => {
|
|
94
|
+
expect(option).toHaveTextContent(
|
|
95
|
+
new RegExp(`${otherCurrencies[index].label}\\s*?${otherCurrencies[index].note}`),
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('shows the currently active currency as active and hides its note', () => {
|
|
101
|
+
customRender({ selectedCurrency: popularCurrencies[0] });
|
|
102
|
+
expect(getTrigger()).toHaveTextContent(popularCurrencies[0].label);
|
|
103
|
+
expect(getTrigger()).not.toHaveTextContent(popularCurrencies[0].note || '');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('calls onCurrencyChange when the user selects a different currency', async () => {
|
|
107
|
+
customRender();
|
|
108
|
+
await openDropdown();
|
|
109
|
+
await userEvent.keyboard('eur{Enter}');
|
|
110
|
+
expect(initialProps.onCurrencyChange).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(initialProps.onCurrencyChange).toHaveBeenCalledWith(popularCurrencies[0]);
|
|
112
|
+
await openDropdown();
|
|
113
|
+
await userEvent.keyboard('gbp{Enter}');
|
|
114
|
+
expect(initialProps.onCurrencyChange).toHaveBeenCalledTimes(2);
|
|
115
|
+
expect(initialProps.onCurrencyChange).toHaveBeenCalledWith(popularCurrencies[2]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('sizing', () => {
|
|
119
|
+
(
|
|
120
|
+
[
|
|
121
|
+
{ label: 'large as default', prop: undefined, suffix: 'lg' },
|
|
122
|
+
{ label: 'large', prop: 'lg', suffix: 'lg' },
|
|
123
|
+
{ label: 'medium', prop: 'md', suffix: 'md' },
|
|
124
|
+
{ label: 'small', prop: 'sm', suffix: 'sm' },
|
|
125
|
+
] as const
|
|
126
|
+
).forEach((props) => {
|
|
127
|
+
it(`should respect ${props.label} size`, () => {
|
|
128
|
+
const { container } = customRender({ size: props.prop });
|
|
129
|
+
expect(container.querySelector('.tw-money-input')).toHaveClass(
|
|
130
|
+
`input-group-${props.suffix}`,
|
|
131
|
+
);
|
|
132
|
+
expect(getTrigger()).toHaveClass(`np-form-control--size-${props.suffix}`);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('when searching', () => {
|
|
138
|
+
it('hides headers', async () => {
|
|
139
|
+
customRender();
|
|
140
|
+
await openDropdown();
|
|
141
|
+
await userEvent.keyboard('dollar');
|
|
142
|
+
expect(screen.queryByRole('group', { name: 'Popular currencies' })).not.toBeInTheDocument();
|
|
143
|
+
expect(
|
|
144
|
+
screen.queryByRole('group', { name: 'Some other currencies' }),
|
|
145
|
+
).not.toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('searches by label', async () => {
|
|
149
|
+
customRender();
|
|
150
|
+
await openDropdown();
|
|
151
|
+
await userEvent.keyboard('gb');
|
|
152
|
+
const options = screen.getAllByRole('option');
|
|
153
|
+
expect(options).toHaveLength(1);
|
|
154
|
+
expect(options[0]).toHaveAccessibleName(new RegExp(popularCurrencies[2].label));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('searches by note', async () => {
|
|
158
|
+
customRender();
|
|
159
|
+
await openDropdown();
|
|
160
|
+
await userEvent.keyboard('dollar');
|
|
161
|
+
const options = screen.getAllByRole('option');
|
|
162
|
+
expect(options).toHaveLength(3);
|
|
163
|
+
expect(options[0]).toHaveAccessibleName(new RegExp(popularCurrencies[1].note!));
|
|
164
|
+
expect(options[1]).toHaveAccessibleName(new RegExp(otherCurrencies[0].note!));
|
|
165
|
+
expect(options[2]).toHaveAccessibleName(new RegExp(otherCurrencies[1].note!));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('searches by searchable string', async () => {
|
|
169
|
+
customRender();
|
|
170
|
+
await openDropdown();
|
|
171
|
+
await userEvent.keyboard('hon');
|
|
172
|
+
const options = screen.getAllByRole('option');
|
|
173
|
+
expect(options).toHaveLength(1);
|
|
174
|
+
expect(options[0]).toHaveAccessibleName(new RegExp(popularCurrencies[1].label));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('custom action', () => {
|
|
178
|
+
const actionProps = {
|
|
179
|
+
onCustomAction: jest.fn(),
|
|
180
|
+
customActionLabel: 'Custom action label',
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
it('does not shows when onCustomAction is not set', async () => {
|
|
184
|
+
customRender({ customActionLabel: actionProps.customActionLabel });
|
|
185
|
+
await openDropdown();
|
|
186
|
+
expect(
|
|
187
|
+
screen.queryByRole('button', { name: actionProps.customActionLabel }),
|
|
188
|
+
).not.toBeInTheDocument();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('shows when onCustomAction and customActionLabel are set', async () => {
|
|
192
|
+
customRender(actionProps);
|
|
193
|
+
await openDropdown();
|
|
194
|
+
expect(
|
|
195
|
+
screen.getByRole('button', { name: actionProps.customActionLabel }),
|
|
196
|
+
).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('triggers onCustomAction but not onCurrencyChange', async () => {
|
|
200
|
+
customRender(actionProps);
|
|
201
|
+
await openDropdown();
|
|
202
|
+
await userEvent.click(screen.getByRole('button', { name: actionProps.customActionLabel }));
|
|
203
|
+
expect(actionProps.onCustomAction).toHaveBeenCalledTimes(1);
|
|
204
|
+
expect(initialProps.onCurrencyChange).not.toHaveBeenCalled();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('sorts by labels first', async () => {
|
|
209
|
+
customRender();
|
|
210
|
+
await openDropdown();
|
|
211
|
+
await userEvent.keyboard('au');
|
|
212
|
+
const options = screen.getAllByRole('option');
|
|
213
|
+
expect(options).toHaveLength(3);
|
|
214
|
+
expect(options[0]).toHaveAccessibleName(new RegExp(otherCurrencies[1].label));
|
|
215
|
+
expect(options[1]).toHaveAccessibleName(new RegExp(popularCurrencies[0].label));
|
|
216
|
+
expect(options[2]).toHaveAccessibleName(new RegExp(popularCurrencies[1].label));
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('amount formatting', () => {
|
|
221
|
+
it('formats the number you input after you blur it', async () => {
|
|
222
|
+
customRender();
|
|
223
|
+
const input = getInput();
|
|
224
|
+
await userEvent.clear(input);
|
|
225
|
+
await userEvent.type(input, '1234567.6543');
|
|
226
|
+
await userEvent.tab();
|
|
227
|
+
expect(input).toHaveValue('1,234,567.65');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('calls onAmountChange with parsed and formatted value', async () => {
|
|
232
|
+
customRender();
|
|
233
|
+
expect(initialProps.onAmountChange).not.toHaveBeenCalled();
|
|
234
|
+
await userEvent.type(getInput(), '.6543');
|
|
235
|
+
expect(initialProps.onAmountChange).toHaveBeenCalledTimes(5);
|
|
236
|
+
expect(initialProps.onAmountChange).toHaveBeenCalledWith(1000.65);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('calls onAmountChange when input value is empty', async () => {
|
|
240
|
+
customRender();
|
|
241
|
+
await userEvent.clear(getInput());
|
|
242
|
+
expect(initialProps.onAmountChange).toHaveBeenCalledTimes(1);
|
|
243
|
+
expect(initialProps.onAmountChange).toHaveBeenCalledWith(null);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('renders addon when element is passed through props', () => {
|
|
247
|
+
const addonElement = <span data-testid="test-addon" />;
|
|
248
|
+
customRender({ addon: addonElement });
|
|
249
|
+
expect(screen.getByTestId('test-addon')).toBeInTheDocument();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('fixed currency', () => {
|
|
253
|
+
const EEK = { value: 'EEK', label: 'EEK', currency: 'eek' };
|
|
254
|
+
|
|
255
|
+
it('shows fixed currency view if currencies array is empty but select currency exists', () => {
|
|
256
|
+
customRender({
|
|
257
|
+
currencies: [],
|
|
258
|
+
selectedCurrency: EEK,
|
|
259
|
+
});
|
|
260
|
+
expect(screen.queryByRole('combobox', { name: /Select currency/ })).not.toBeInTheDocument();
|
|
261
|
+
expect(screen.getByText(EEK.label)).toBeInTheDocument();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('shows fixed currency view if only one currency available and selected', () => {
|
|
265
|
+
customRender({
|
|
266
|
+
currencies: [EEK],
|
|
267
|
+
selectedCurrency: EEK,
|
|
268
|
+
});
|
|
269
|
+
expect(screen.queryByRole('combobox', { name: /Select currency/ })).not.toBeInTheDocument();
|
|
270
|
+
expect(screen.getByText(EEK.label)).toBeInTheDocument();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('shows fixed currency view when no function is passed to onCurrencyChange prop', () => {
|
|
274
|
+
customRender({ onCurrencyChange: undefined });
|
|
275
|
+
expect(screen.queryByRole('combobox', { name: /Select currency/ })).not.toBeInTheDocument();
|
|
276
|
+
expect(screen.getByText(popularCurrencies[1].label)).toBeInTheDocument();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('currency keyline and flag', () => {
|
|
280
|
+
(
|
|
281
|
+
[
|
|
282
|
+
['lg', true],
|
|
283
|
+
['md', true],
|
|
284
|
+
['sm', false],
|
|
285
|
+
] as const
|
|
286
|
+
).forEach(([size, isShown]) => {
|
|
287
|
+
it(`${isShown ? 'shows' : `doesn't show`} for '${size}' input`, () => {
|
|
288
|
+
const { container } = customRender({
|
|
289
|
+
currencies: [EEK],
|
|
290
|
+
selectedCurrency: EEK,
|
|
291
|
+
size,
|
|
292
|
+
});
|
|
293
|
+
expect(container.querySelector('.wds-flag-eek'))[isShown ? 'toBeTruthy' : 'toBeFalsy']();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('disabled state', () => {
|
|
299
|
+
it('should be enable by default', () => {
|
|
300
|
+
customRender({
|
|
301
|
+
currencies: [EEK],
|
|
302
|
+
selectedCurrency: EEK,
|
|
303
|
+
});
|
|
304
|
+
expect(getInput()).toBeEnabled();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should be disabled when there is no onAmountChange prop', () => {
|
|
308
|
+
customRender({
|
|
309
|
+
currencies: [EEK],
|
|
310
|
+
selectedCurrency: EEK,
|
|
311
|
+
onAmountChange: undefined,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(getInput()).toBeDisabled();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('uses the search placeholder', async () => {
|
|
320
|
+
const searchPlaceholder = 'custom placeholder';
|
|
321
|
+
customRender({ searchPlaceholder });
|
|
322
|
+
await openDropdown();
|
|
323
|
+
expect(screen.getByRole('combobox', { name: searchPlaceholder })).toBeInTheDocument();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('calls onSearchChange', async () => {
|
|
327
|
+
const searchQuery = 'aus';
|
|
328
|
+
customRender();
|
|
329
|
+
await openDropdown();
|
|
330
|
+
await userEvent.keyboard(searchQuery);
|
|
331
|
+
|
|
332
|
+
expect(initialProps.onSearchChange).toHaveBeenCalledTimes(searchQuery.length);
|
|
333
|
+
expect(initialProps.onSearchChange).toHaveBeenCalledWith({
|
|
334
|
+
filteredOptions: [popularCurrencies[0], otherCurrencies[1]],
|
|
335
|
+
searchQuery,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await userEvent.keyboard('{Backspace}{Backspace}{Backspace}');
|
|
339
|
+
expect(initialProps.onSearchChange).toHaveBeenCalledWith({
|
|
340
|
+
filteredOptions: currencies,
|
|
341
|
+
searchQuery: '',
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('ensures namespaced classNames can be provided and used', () => {
|
|
346
|
+
const classNames = { 'tw-money-input': 'customClass' };
|
|
347
|
+
customRender({ classNames });
|
|
348
|
+
expect(screen.getAllByRole('group')[0]).toHaveClass(classNames['tw-money-input']);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe('placeholder', () => {
|
|
352
|
+
it('shows a formatted placeholder when provided', () => {
|
|
353
|
+
customRender({ placeholder: 12345767.6543 });
|
|
354
|
+
expect(getInput()).toHaveAttribute('placeholder', '12,345,767.65');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('allows a placeholder of 0', () => {
|
|
358
|
+
customRender({ placeholder: 0 });
|
|
359
|
+
expect(getInput()).toHaveAttribute('placeholder', '0');
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('should respect selectProps', async () => {
|
|
364
|
+
customRender({
|
|
365
|
+
selectProps: {
|
|
366
|
+
className: 'selectClassName',
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
expect(screen.getAllByRole('group')[1]).toHaveClass('selectClassName');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('text input', () => {
|
|
373
|
+
it.each([
|
|
374
|
+
['asd', ''],
|
|
375
|
+
['1a2s3d', '123'],
|
|
376
|
+
['±!@#$^*_+?><', ''],
|
|
377
|
+
['1±!@#$^*,_+?><2', '1,2'],
|
|
378
|
+
['12,3', '12,3'],
|
|
379
|
+
['12.3', '12.3'],
|
|
380
|
+
])("ignores the letters when typed '%s' and shows '%s'", async (testValue, expectedValue) => {
|
|
381
|
+
customRender({ amount: null });
|
|
382
|
+
await userEvent.type(getInput(), testValue);
|
|
383
|
+
expect(getInput()).toHaveValue(expectedValue);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('supports custom `aria-labelledby` attribute', () => {
|
|
388
|
+
render(
|
|
389
|
+
<>
|
|
390
|
+
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
|
391
|
+
<label id="prioritized-label">Prioritized label</label>
|
|
392
|
+
<MoneyInput {...initialProps} aria-labelledby="prioritized-label" />
|
|
393
|
+
</>,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
expect(screen.getAllByLabelText('Prioritized label')[0]).toHaveClass('input-group');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('supports `Field` for labeling', () => {
|
|
400
|
+
render(
|
|
401
|
+
<Field label="Recipient gets">
|
|
402
|
+
<MoneyInput {...initialProps} />
|
|
403
|
+
</Field>,
|
|
404
|
+
);
|
|
405
|
+
expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Recipient gets/);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
describe('ids', () => {
|
|
409
|
+
it('renders Select component with generated id when id is not provided', () => {
|
|
410
|
+
customRender();
|
|
411
|
+
expect(getTrigger()).toHaveAttribute('id');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('passes the id given to the input element', () => {
|
|
415
|
+
customRender({ id: 'some-id' });
|
|
416
|
+
expect(getInput()).toHaveAttribute('id', 'some-id');
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should guarantee id and connect the input with the selected currency via withId HoC', () => {
|
|
420
|
+
customRender();
|
|
421
|
+
expect(getInput().getAttribute('id')).toBeTruthy();
|
|
422
|
+
expect(getInput()).toHaveAttribute('aria-describedby', getTrigger().getAttribute('id'));
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should have unique id for the select filter with predefined id', async () => {
|
|
426
|
+
const fieldId = 'myFieldId';
|
|
427
|
+
const searchPlaceholder = 'Type a currency or country';
|
|
428
|
+
render(
|
|
429
|
+
<Field label="Multiple currencies" id={fieldId}>
|
|
430
|
+
<MoneyInput {...initialProps} searchPlaceholder={searchPlaceholder} />
|
|
431
|
+
</Field>,
|
|
432
|
+
);
|
|
433
|
+
await openDropdown();
|
|
434
|
+
expect(screen.getByLabelText(searchPlaceholder)).toHaveAttribute(
|
|
435
|
+
'id',
|
|
436
|
+
`${fieldId}SelectedCurrencySearch`,
|
|
437
|
+
);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should have unique id for the select filter without predefined id', async () => {
|
|
441
|
+
const searchPlaceholder = 'Type a currency or country';
|
|
442
|
+
render(
|
|
443
|
+
<Field label="Multiple currencies">
|
|
444
|
+
<MoneyInput {...initialProps} searchPlaceholder={searchPlaceholder} />
|
|
445
|
+
</Field>,
|
|
446
|
+
);
|
|
447
|
+
await openDropdown();
|
|
448
|
+
expect(screen.getByLabelText(searchPlaceholder)).toHaveAttribute(
|
|
449
|
+
'id',
|
|
450
|
+
expect.stringMatching(/^:.*?:SelectedCurrencySearch$/),
|
|
451
|
+
);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('should have AT label for the currency dropdown', () => {
|
|
456
|
+
customRender();
|
|
457
|
+
expect(getTrigger()).toHaveAttribute('aria-label', messages.selectCurrencyLabel.defaultMessage);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should have a listbox label', async () => {
|
|
461
|
+
customRender();
|
|
462
|
+
const trigger = getTrigger();
|
|
463
|
+
await openDropdown();
|
|
464
|
+
const triggerLabel = trigger.getAttribute('aria-label');
|
|
465
|
+
expect(triggerLabel).toBeTruthy();
|
|
466
|
+
expect(screen.getByRole('listbox', { name: triggerLabel ?? '' })).toBeInTheDocument();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
@@ -97,7 +97,8 @@ export interface MoneyInputProps extends WrappedComponentProps {
|
|
|
97
97
|
selectProps?: Partial<SelectInputProps<CurrencyOptionItem>>;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
type MoneyInputPropsWithInputAttributes = MoneyInputProps &
|
|
100
|
+
export type MoneyInputPropsWithInputAttributes = MoneyInputProps &
|
|
101
|
+
Partial<WithInputAttributesProps>;
|
|
101
102
|
|
|
102
103
|
interface MoneyInputState {
|
|
103
104
|
searchQuery: string;
|