@transferwise/components 46.84.1 → 46.86.0
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/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/dimmer/Dimmer.js.map +1 -1
- package/build/dimmer/Dimmer.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/en.json +1 -0
- package/build/i18n/en.json.js +1 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +1 -0
- package/build/i18n/en.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 +6 -0
- package/build/i18n/th.json.js +6 -0
- package/build/i18n/th.json.js.map +1 -1
- package/build/i18n/th.json.mjs +6 -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 +11 -0
- package/build/i18n/zh-CN.json.js +11 -0
- package/build/i18n/zh-CN.json.js.map +1 -1
- package/build/i18n/zh-CN.json.mjs +11 -0
- package/build/i18n/zh-CN.json.mjs.map +1 -1
- package/build/i18n/zh-HK.json +5 -0
- package/build/i18n/zh-HK.json.js +5 -0
- package/build/i18n/zh-HK.json.js.map +1 -1
- package/build/i18n/zh-HK.json.mjs +5 -0
- package/build/i18n/zh-HK.json.mjs.map +1 -1
- package/build/main.css +17 -158
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/stepper/Stepper.js +6 -3
- package/build/stepper/Stepper.js.map +1 -1
- package/build/stepper/Stepper.mjs +6 -3
- package/build/stepper/Stepper.mjs.map +1 -1
- package/build/styles/circularButton/CircularButton.css +17 -158
- package/build/styles/main.css +17 -158
- package/build/types/avatarLayout/AvatarLayout.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/dimmer/Dimmer.d.ts +1 -1
- package/build/types/dimmer/Dimmer.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/build/types/stepper/Stepper.d.ts +2 -1
- package/build/types/stepper/Stepper.d.ts.map +1 -1
- package/build/types/test-utils/index.d.ts +2 -0
- package/build/types/test-utils/index.d.ts.map +1 -1
- package/build/types/uploadInput/uploadButton/UploadButton.messages.d.ts +5 -0
- package/build/types/uploadInput/uploadButton/UploadButton.messages.d.ts.map +1 -1
- package/build/uploadInput/uploadButton/UploadButton.js +3 -1
- package/build/uploadInput/uploadButton/UploadButton.js.map +1 -1
- package/build/uploadInput/uploadButton/UploadButton.messages.js +3 -0
- package/build/uploadInput/uploadButton/UploadButton.messages.js.map +1 -1
- package/build/uploadInput/uploadButton/UploadButton.messages.mjs +3 -0
- package/build/uploadInput/uploadButton/UploadButton.messages.mjs.map +1 -1
- package/build/uploadInput/uploadButton/UploadButton.mjs +3 -1
- package/build/uploadInput/uploadButton/UploadButton.mjs.map +1 -1
- package/package.json +5 -5
- package/src/avatar/Avatar.story.tsx +4 -1
- package/src/avatarLayout/AvatarLayout.story.tsx +2 -0
- package/src/avatarLayout/AvatarLayout.tsx +3 -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/definitionList/DefinitionList.story.tsx +57 -57
- package/src/definitionList/DefinitionList.tsx +1 -1
- package/src/dimmer/{Dimmer.rtl.spec.tsx → Dimmer.spec.tsx} +33 -29
- package/src/dimmer/Dimmer.tsx +4 -4
- package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +3 -1
- package/src/i18n/de.json +1 -0
- package/src/i18n/en.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 +6 -0
- package/src/i18n/tr.json +1 -0
- package/src/i18n/zh-CN.json +11 -0
- package/src/i18n/zh-HK.json +5 -0
- package/src/iconButton/IconButton.story.tsx +6 -6
- package/src/main.css +17 -158
- package/src/moneyInput/MoneyInput.spec.tsx +468 -0
- package/src/moneyInput/MoneyInput.tsx +2 -1
- package/src/navigationOption/NavigationOption.spec.tsx +113 -0
- package/src/phoneNumberInput/PhoneNumberInput.spec.tsx +283 -0
- package/src/radioOption/RadioOption.spec.tsx +73 -0
- package/src/slidingPanel/SlidingPanel.spec.tsx +69 -0
- package/src/stepper/Stepper.spec.tsx +236 -0
- package/src/stepper/Stepper.tests.story.tsx +89 -0
- package/src/stepper/Stepper.tsx +9 -4
- package/src/stepper/{deviceDetection.spec.js → deviceDetection.spec.ts} +6 -3
- package/src/uploadInput/uploadButton/UploadButton.messages.ts +7 -0
- package/src/uploadInput/uploadButton/UploadButton.tsx +1 -1
- package/src/circularButton/_button-label-states.less +0 -34
- package/src/definitionList/DefinitionList.spec.js +0 -91
- package/src/dimmer/Dimmer.spec.js +0 -87
- package/src/moneyInput/MoneyInput.rtl.spec.tsx +0 -149
- package/src/moneyInput/MoneyInput.spec.js +0 -820
- package/src/navigationOption/NavigationOption.spec.js +0 -93
- package/src/phoneNumberInput/PhoneNumberInput.rtl.spec.tsx +0 -32
- package/src/phoneNumberInput/PhoneNumberInput.spec.js +0 -356
- package/src/radioOption/RadioOption.spec.js +0 -67
- package/src/slidingPanel/SlidingPanel.spec.js +0 -56
- package/src/stepper/Stepper.spec.js +0 -233
- /package/src/alert/{Alert.spec.story.tsx → Alert.tests.story.tsx} +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
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
|
+
|
|
13
|
+
mockMatchMedia();
|
|
14
|
+
mockResizeObserver();
|
|
15
|
+
|
|
16
|
+
describe('PhoneNumberInput', () => {
|
|
17
|
+
afterEach(jest.clearAllMocks);
|
|
18
|
+
|
|
19
|
+
const props = { onChange: jest.fn() };
|
|
20
|
+
|
|
21
|
+
const customRender = (overrides: Partial<PhoneNumberInputProps> = {}, locale?: string) =>
|
|
22
|
+
render(<PhoneNumberInput {...props} {...overrides} />, { locale });
|
|
23
|
+
|
|
24
|
+
const getPrefixEl = () => screen.getByRole('combobox');
|
|
25
|
+
const getInputEl = () => screen.getByRole('textbox');
|
|
26
|
+
|
|
27
|
+
describe('defaults', () => {
|
|
28
|
+
it('should set prefix control to default UK value', () => {
|
|
29
|
+
customRender();
|
|
30
|
+
expect(getPrefixEl()).toHaveTextContent('+44');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should set number control to empty', () => {
|
|
34
|
+
customRender();
|
|
35
|
+
expect(getInputEl()).toHaveValue('');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should not disable the select', () => {
|
|
39
|
+
customRender();
|
|
40
|
+
expect(getPrefixEl()).toBeEnabled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should not disable the input', () => {
|
|
44
|
+
customRender();
|
|
45
|
+
expect(getInputEl()).toBeEnabled();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should propagate `initialValue`', () => {
|
|
50
|
+
const prefix = '+39';
|
|
51
|
+
const number = '123456789';
|
|
52
|
+
customRender({ initialValue: `${prefix}${number}` });
|
|
53
|
+
expect(getPrefixEl()).toHaveTextContent(prefix);
|
|
54
|
+
expect(getInputEl()).toHaveValue(number);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('id prop', () => {
|
|
58
|
+
it('should not render id by default', () => {
|
|
59
|
+
customRender();
|
|
60
|
+
expect(getPrefixEl()).not.toHaveAttribute('id');
|
|
61
|
+
expect(getInputEl()).not.toHaveAttribute('id');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should respect `id` for the input and ignore the select', () => {
|
|
65
|
+
const id = 'component-id';
|
|
66
|
+
customRender({ id });
|
|
67
|
+
expect(getPrefixEl()).not.toHaveAttribute('id');
|
|
68
|
+
expect(getInputEl()).toHaveAttribute('id', id);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('pasting', () => {
|
|
73
|
+
const initialPrefix = '+48';
|
|
74
|
+
const initialNumber = '987654321';
|
|
75
|
+
|
|
76
|
+
const renderAndPaste = async (value: string) => {
|
|
77
|
+
customRender({ initialValue: `${initialPrefix}${initialNumber}` });
|
|
78
|
+
await userEvent.tab();
|
|
79
|
+
await userEvent.tab();
|
|
80
|
+
await userEvent.paste(value);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
[
|
|
84
|
+
{ number: '+36303932551', countryCode: '+36', localNumber: '303932551' },
|
|
85
|
+
{ number: '+39123456781', countryCode: '+39', localNumber: '123456781' },
|
|
86
|
+
{ number: '+44 7700 900415', countryCode: '+44', localNumber: '7700900415' },
|
|
87
|
+
{ number: '+2975557308515', countryCode: '+297', localNumber: '5557308515' },
|
|
88
|
+
{ number: '+297-555-7217-360', countryCode: '+297', localNumber: '5557217360' },
|
|
89
|
+
{ number: '+213-555-5160-67', countryCode: '+213', localNumber: '555516067' },
|
|
90
|
+
{ number: '+246-387-5553', countryCode: '+246', localNumber: '3875553' },
|
|
91
|
+
{ number: '+852-940-5558--6', countryCode: '+852', localNumber: '94055586' },
|
|
92
|
+
{ number: '+228 253 5558 4', countryCode: '+228', localNumber: '25355584' },
|
|
93
|
+
].forEach(({ number, countryCode, localNumber }) => {
|
|
94
|
+
it(`'${number}' number should update the value properly`, async () => {
|
|
95
|
+
await renderAndPaste(number);
|
|
96
|
+
|
|
97
|
+
expect(getPrefixEl()).toHaveTextContent(countryCode);
|
|
98
|
+
expect(getInputEl()).toHaveValue(localNumber);
|
|
99
|
+
expect(props.onChange).toHaveBeenCalledWith(number.replace(/[\s-]+/g, ''), countryCode);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should not paste invalid characters', async () => {
|
|
104
|
+
await renderAndPaste('+36asdasdasd');
|
|
105
|
+
expect(getPrefixEl()).toHaveTextContent(initialPrefix);
|
|
106
|
+
expect(getInputEl()).toHaveValue(initialNumber);
|
|
107
|
+
expect(props.onChange).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should not paste countries which are not in the select', async () => {
|
|
111
|
+
await renderAndPaste('+9992342343423');
|
|
112
|
+
expect(getPrefixEl()).toHaveTextContent(initialPrefix);
|
|
113
|
+
expect(getInputEl()).toHaveValue(initialNumber);
|
|
114
|
+
expect(props.onChange).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should not paste numbers which doesn't start with the country code", async () => {
|
|
118
|
+
await renderAndPaste('0+36303932551');
|
|
119
|
+
expect(getPrefixEl()).toHaveTextContent(initialPrefix);
|
|
120
|
+
expect(getInputEl()).toHaveValue(initialNumber);
|
|
121
|
+
expect(props.onChange).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should allow pasting numbers which don't contain a country code", async () => {
|
|
125
|
+
const newNumber = '06303932551';
|
|
126
|
+
await renderAndPaste(newNumber);
|
|
127
|
+
expect(getPrefixEl()).toHaveTextContent(initialPrefix);
|
|
128
|
+
expect(getInputEl()).toHaveValue(newNumber);
|
|
129
|
+
expect(props.onChange).toHaveBeenCalledWith(`${initialPrefix}${newNumber}`, initialPrefix);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('initialValue', () => {
|
|
134
|
+
describe('when a model is supplied that could match more than one prefix', () => {
|
|
135
|
+
const initialProps = { initialValue: '+1868123456789' };
|
|
136
|
+
|
|
137
|
+
it('should set the select to the longest matching prefix', () => {
|
|
138
|
+
customRender(initialProps);
|
|
139
|
+
expect(getPrefixEl()).toHaveTextContent('+1868');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should set the number input to the rest of the number', () => {
|
|
143
|
+
customRender(initialProps);
|
|
144
|
+
expect(getInputEl()).toHaveValue('123456789');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('when a model is supplied with no matching prefix', () => {
|
|
149
|
+
const initialProps = { initialValue: '+999123456789' };
|
|
150
|
+
|
|
151
|
+
it('should empty the select', () => {
|
|
152
|
+
customRender(initialProps);
|
|
153
|
+
expect(getPrefixEl()).toHaveTextContent('Select an option...');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should put the whole value in the input without the plus', () => {
|
|
157
|
+
customRender(initialProps);
|
|
158
|
+
expect(getInputEl()).toHaveValue('999123456789');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('when an invalid model is supplied', () => {
|
|
163
|
+
it('should not re-explode model', () => {
|
|
164
|
+
customRender({ initialValue: '+123' });
|
|
165
|
+
expect(getPrefixEl()).toHaveTextContent('+44');
|
|
166
|
+
expect(getInputEl()).toHaveValue('');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('when disabled is true', () => {
|
|
172
|
+
it('should disable both controls', () => {
|
|
173
|
+
customRender({ disabled: true });
|
|
174
|
+
expect(getPrefixEl()).toBeDisabled();
|
|
175
|
+
expect(getInputEl()).toBeDisabled();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('placeholders', () => {
|
|
180
|
+
it('should use the provided placeholder', () => {
|
|
181
|
+
const placeholder = 'custom placeholder';
|
|
182
|
+
customRender({ placeholder });
|
|
183
|
+
expect(getInputEl()).toHaveAttribute('placeholder', placeholder);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should use the provided searchPlaceholder', async () => {
|
|
187
|
+
const searchPlaceholder = 'search placeholder';
|
|
188
|
+
customRender({ searchPlaceholder });
|
|
189
|
+
await userEvent.click(getPrefixEl());
|
|
190
|
+
expect(screen.getByRole('combobox', { name: searchPlaceholder })).toBeInTheDocument();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('when supplied with a locale', () => {
|
|
195
|
+
describe('and a value', () => {
|
|
196
|
+
it('should use the prefix of the supplied value', () => {
|
|
197
|
+
customRender({ initialValue: '+12345678' }, 'es');
|
|
198
|
+
expect(getPrefixEl()).toHaveTextContent('+1');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('and no value', () => {
|
|
203
|
+
describe('and no country code', () => {
|
|
204
|
+
it('should default the prefix to the local country', () => {
|
|
205
|
+
customRender(undefined, 'es');
|
|
206
|
+
expect(getPrefixEl()).toHaveTextContent('+34');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('and country code', () => {
|
|
211
|
+
it('should override locale prefix with country specific prefix', () => {
|
|
212
|
+
customRender({ countryCode: 'US' }, 'es');
|
|
213
|
+
expect(getPrefixEl()).toHaveTextContent('+1');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('user input', () => {
|
|
220
|
+
describe('valid number', () => {
|
|
221
|
+
it('should trigger onChange handler', async () => {
|
|
222
|
+
customRender();
|
|
223
|
+
await userEvent.type(getInputEl(), '123');
|
|
224
|
+
expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('invalid number', () => {
|
|
229
|
+
it('should trigger onChange with null value', async () => {
|
|
230
|
+
customRender({ initialValue: '+1234' });
|
|
231
|
+
await userEvent.type(getInputEl(), '{Backspace}{Backspace}{Backspace}1');
|
|
232
|
+
expect(props.onChange).toHaveBeenCalledWith(null, '+1');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('when user insert invalid character', () => {
|
|
237
|
+
it('should strip them', async () => {
|
|
238
|
+
customRender({ initialValue: '+12345678' });
|
|
239
|
+
await userEvent.type(getInputEl(), '123--');
|
|
240
|
+
expect(props.onChange).toHaveBeenCalledWith('+12345678123', '+1');
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('overlapping prefix and suffix numbers', () => {
|
|
245
|
+
it("shouldn't change the prefix number on matching suffix input", async () => {
|
|
246
|
+
customRender({ countryCode: 'eg' });
|
|
247
|
+
await userEvent.type(getInputEl(), '1111111');
|
|
248
|
+
expect(getInputEl()).toHaveValue('1111111');
|
|
249
|
+
expect(props.onChange).toHaveBeenCalledWith('+201111111', '+20');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('when selectProps is supplied', () => {
|
|
255
|
+
it('renders Select component with expected props', () => {
|
|
256
|
+
customRender({ selectProps: { className: 'custom-class' } });
|
|
257
|
+
expect(getPrefixEl().parentElement).toHaveClass('custom-class');
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('supports custom `aria-labelledby` attribute', () => {
|
|
262
|
+
render(
|
|
263
|
+
<>
|
|
264
|
+
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
|
265
|
+
<label id="prioritized-label">Prioritized label</label>
|
|
266
|
+
<PhoneNumberInput aria-labelledby="prioritized-label" onChange={() => {}} />
|
|
267
|
+
</>,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
expect(
|
|
271
|
+
within(screen.getByLabelText('Prioritized label')).getByRole('textbox'),
|
|
272
|
+
).toBeInTheDocument();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('supports `Field` for labeling', () => {
|
|
276
|
+
render(
|
|
277
|
+
<Field label="Phone number">
|
|
278
|
+
<PhoneNumberInput initialValue="+12345678" onChange={() => {}} />
|
|
279
|
+
</Field>,
|
|
280
|
+
);
|
|
281
|
+
expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Phone number/);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { render, screen, userEvent } from '../test-utils';
|
|
2
|
+
import RadioOption, { type RadioOptionProps } from '.';
|
|
3
|
+
|
|
4
|
+
describe('Radio option', () => {
|
|
5
|
+
const initialProps = {
|
|
6
|
+
id: 'componentId',
|
|
7
|
+
name: 'componentName',
|
|
8
|
+
title: 'Component Title',
|
|
9
|
+
content: <img alt="contentImage" />,
|
|
10
|
+
onChange: jest.fn(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const customRender = (overrides: Partial<RadioOptionProps> = {}) =>
|
|
14
|
+
render(<RadioOption {...initialProps} {...overrides} />);
|
|
15
|
+
|
|
16
|
+
it('should render `media`', () => {
|
|
17
|
+
const Icon = <img alt="media" />;
|
|
18
|
+
customRender({ media: Icon });
|
|
19
|
+
expect(screen.getByRole('img', { name: 'media' })).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('passes props to radio button', () => {
|
|
23
|
+
customRender();
|
|
24
|
+
expect(screen.getByRole('radio')).toHaveAttribute('id', initialProps.id);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('passes name to radio button', () => {
|
|
28
|
+
customRender();
|
|
29
|
+
expect(screen.getByRole('radio')).toHaveAttribute('name', initialProps.name);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('checked', () => {
|
|
33
|
+
it('should be `false` by default', () => {
|
|
34
|
+
customRender();
|
|
35
|
+
expect(screen.getByRole('radio')).not.toBeChecked();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should respect the prop', () => {
|
|
39
|
+
customRender({ checked: true });
|
|
40
|
+
expect(screen.getByRole('radio')).toBeChecked();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('passes change handler to radio button', async () => {
|
|
45
|
+
customRender();
|
|
46
|
+
await userEvent.click(screen.getByRole('radio'));
|
|
47
|
+
expect(initialProps.onChange).toHaveBeenCalledTimes(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('disabled', () => {
|
|
51
|
+
it('should be `false` by default', () => {
|
|
52
|
+
customRender();
|
|
53
|
+
expect(screen.getByRole('radio')).toBeEnabled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should respect the prop', () => {
|
|
57
|
+
customRender({ disabled: true });
|
|
58
|
+
expect(screen.getByRole('radio')).toBeDisabled();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('isContainerAligned', () => {
|
|
63
|
+
it('should not be aligned by default', () => {
|
|
64
|
+
const { container } = customRender();
|
|
65
|
+
expect(container.querySelector('.np-option')).not.toHaveClass('np-option__container-aligned');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders aligned with container content', () => {
|
|
69
|
+
const { container } = customRender({ isContainerAligned: true });
|
|
70
|
+
expect(container.querySelector('.np-option')).toHaveClass('np-option__container-aligned');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { render, screen } from '../test-utils';
|
|
2
|
+
import SlidingPanel, { SlidingPanelProps } from './SlidingPanel';
|
|
3
|
+
|
|
4
|
+
describe('SlidingPanel', () => {
|
|
5
|
+
const initialProps: SlidingPanelProps = {
|
|
6
|
+
open: true,
|
|
7
|
+
children: <div data-testid="content">Content</div>,
|
|
8
|
+
testId: 'wrapper',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const customRender = (overrides: Partial<SlidingPanelProps> = {}) =>
|
|
12
|
+
render(<SlidingPanel {...initialProps} {...overrides} />);
|
|
13
|
+
|
|
14
|
+
describe('open', () => {
|
|
15
|
+
it('should respect open=false', () => {
|
|
16
|
+
customRender({ open: false });
|
|
17
|
+
expect(screen.queryByTestId('wrapper')).not.toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should respect open=true', () => {
|
|
21
|
+
customRender();
|
|
22
|
+
expect(screen.getByTestId('wrapper')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('position', () => {
|
|
27
|
+
it(`should default to 'left'`, () => {
|
|
28
|
+
customRender();
|
|
29
|
+
expect(screen.queryByTestId('wrapper')).toHaveClass(`sliding-panel--open-left`);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
(['left', 'right', 'top', 'bottom'] as const).forEach((position) => {
|
|
33
|
+
describe(position, () => {
|
|
34
|
+
it(`should add the classname`, () => {
|
|
35
|
+
customRender({ position });
|
|
36
|
+
expect(screen.queryByTestId('wrapper')).toHaveClass(`sliding-panel--open-${position}`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it(`should add the classname for 'showSlidingPanelBorder'`, () => {
|
|
40
|
+
customRender({ position, showSlidingPanelBorder: true });
|
|
41
|
+
expect(screen.queryByTestId('wrapper')).toHaveClass(`sliding-panel--border-${position}`);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should render children', () => {
|
|
48
|
+
customRender();
|
|
49
|
+
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should respect className', () => {
|
|
53
|
+
const className = 'customClass';
|
|
54
|
+
customRender({ className });
|
|
55
|
+
expect(screen.getByTestId('wrapper')).toHaveClass(className);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('slidingPanelPositionFixed', () => {
|
|
59
|
+
it('should not be fixed by default', () => {
|
|
60
|
+
customRender();
|
|
61
|
+
expect(screen.getByTestId('wrapper')).not.toHaveClass('sliding-panel--fixed');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should respect slidingPanelPositionFixed=true', () => {
|
|
65
|
+
customRender({ slidingPanelPositionFixed: true });
|
|
66
|
+
expect(screen.getByTestId('wrapper')).toHaveClass('sliding-panel--fixed');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { render, screen, userEvent } from '../test-utils';
|
|
2
|
+
import * as mockedDeviceDetection from './deviceDetection';
|
|
3
|
+
|
|
4
|
+
import Stepper from './Stepper';
|
|
5
|
+
|
|
6
|
+
jest.mock('./deviceDetection', () => ({
|
|
7
|
+
isTouchDevice: jest.fn(() => false),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe('Stepper', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const generateSteps = (stepsCount: number) =>
|
|
16
|
+
Array.from({ length: stepsCount }, () => ({
|
|
17
|
+
label: Math.random().toString(),
|
|
18
|
+
onClick: jest.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const getSteps = () => screen.getAllByRole('listitem');
|
|
22
|
+
|
|
23
|
+
const initialProps = {
|
|
24
|
+
activeStep: 0,
|
|
25
|
+
steps: generateSteps(3),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const customRender = (overrides = {}) => {
|
|
29
|
+
return render(<Stepper {...initialProps} {...overrides} />);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe('progress bar', () => {
|
|
33
|
+
it('renders nothing when no steps are passed in', () => {
|
|
34
|
+
customRender({ steps: [] });
|
|
35
|
+
expect(screen.queryByTestId('progress-bar')).not.toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('steps', () => {
|
|
40
|
+
it('have rendered labels', () => {
|
|
41
|
+
const steps = generateSteps(5);
|
|
42
|
+
customRender({ steps });
|
|
43
|
+
|
|
44
|
+
getSteps().forEach((step, index) => {
|
|
45
|
+
expect(step).toHaveTextContent(steps[index].label);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('step interactive style', () => {
|
|
50
|
+
const expectStepIsVisuallyInteractive = (stepIndex: number) => {
|
|
51
|
+
// eslint-disable-next-line jest/valid-expect
|
|
52
|
+
return expect(getSteps()[stepIndex].classList.contains('tw-stepper__step--clickable'));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
it('is not styled as interactive if it is the active step', async () => {
|
|
56
|
+
const steps = [
|
|
57
|
+
{ label: '0' },
|
|
58
|
+
{ label: '1', onClick: jest.fn() },
|
|
59
|
+
{ label: '2', onClick: jest.fn() },
|
|
60
|
+
];
|
|
61
|
+
customRender({ steps, activeStep: 0 });
|
|
62
|
+
expectStepIsVisuallyInteractive(0).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('is not styled as interactive if not the active step and has no click handler', () => {
|
|
66
|
+
const steps = [
|
|
67
|
+
{ label: '0' },
|
|
68
|
+
{ label: '1', onClick: jest.fn() },
|
|
69
|
+
{ label: '2', onClick: jest.fn() },
|
|
70
|
+
];
|
|
71
|
+
customRender({ steps, activeStep: 1 });
|
|
72
|
+
expectStepIsVisuallyInteractive(0).toBe(false);
|
|
73
|
+
expectStepIsVisuallyInteractive(1).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('is styled as interactive if not the active step and has click handler', () => {
|
|
77
|
+
const steps = [
|
|
78
|
+
{ label: '0', onClick: jest.fn() },
|
|
79
|
+
{ label: '1', onClick: jest.fn() },
|
|
80
|
+
{ label: '2', onClick: jest.fn() },
|
|
81
|
+
];
|
|
82
|
+
customRender({ steps, activeStep: 2 });
|
|
83
|
+
expectStepIsVisuallyInteractive(0).toBe(true);
|
|
84
|
+
expectStepIsVisuallyInteractive(1).toBe(true);
|
|
85
|
+
expectStepIsVisuallyInteractive(2).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('step interactivity', () => {
|
|
90
|
+
const getStepChild = (stepIndex: number) => getSteps()[stepIndex].children[0];
|
|
91
|
+
|
|
92
|
+
it('is not interactive if it is the active step', async () => {
|
|
93
|
+
const steps = [
|
|
94
|
+
{ label: '0', onClick: jest.fn() },
|
|
95
|
+
{ label: '1', onClick: jest.fn() },
|
|
96
|
+
{ label: '2', onClick: jest.fn() },
|
|
97
|
+
];
|
|
98
|
+
customRender({ steps, activeStep: 0 });
|
|
99
|
+
await userEvent.click(getStepChild(0));
|
|
100
|
+
expect(steps[0].onClick).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('is not interactive if not the active step but has no click handler', async () => {
|
|
104
|
+
const steps = [
|
|
105
|
+
{ label: '0' },
|
|
106
|
+
{ label: '1', onClick: jest.fn() },
|
|
107
|
+
{ label: '2', onClick: jest.fn() },
|
|
108
|
+
];
|
|
109
|
+
customRender({ steps, activeStep: 1 });
|
|
110
|
+
await userEvent.click(getStepChild(0));
|
|
111
|
+
expect(steps[1].onClick).not.toHaveBeenCalled();
|
|
112
|
+
await userEvent.click(getStepChild(1));
|
|
113
|
+
expect(steps[1].onClick).not.toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('is interactive if not the active step and has click handler', async () => {
|
|
117
|
+
const steps = [
|
|
118
|
+
{ label: '0', onClick: jest.fn() },
|
|
119
|
+
{ label: '1', onClick: jest.fn() },
|
|
120
|
+
{ label: '2', onClick: jest.fn() },
|
|
121
|
+
];
|
|
122
|
+
customRender({ steps, activeStep: 2 });
|
|
123
|
+
await userEvent.click(getStepChild(1));
|
|
124
|
+
expect(steps[1].onClick).toHaveBeenCalledTimes(1);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('are not clickable when active', async () => {
|
|
129
|
+
const clickedOnFirstStep = jest.fn();
|
|
130
|
+
const clickedOnSecondStep = jest.fn();
|
|
131
|
+
const initialProps = {
|
|
132
|
+
steps: [
|
|
133
|
+
{ label: 'one', onClick: clickedOnFirstStep },
|
|
134
|
+
{ label: 'two', onClick: clickedOnSecondStep },
|
|
135
|
+
],
|
|
136
|
+
activeStep: 0,
|
|
137
|
+
};
|
|
138
|
+
const { rerender } = customRender(initialProps);
|
|
139
|
+
|
|
140
|
+
const clickOnStep = async (stepIndex: number) => {
|
|
141
|
+
const step = screen.getByText(initialProps.steps[stepIndex].label).parentElement;
|
|
142
|
+
if (step) {
|
|
143
|
+
return userEvent.click(step);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
await clickOnStep(0);
|
|
148
|
+
expect(clickedOnFirstStep).not.toHaveBeenCalled();
|
|
149
|
+
|
|
150
|
+
rerender(<Stepper {...initialProps} activeStep={1} />);
|
|
151
|
+
await clickOnStep(0);
|
|
152
|
+
expect(clickedOnFirstStep).toHaveBeenCalledTimes(1);
|
|
153
|
+
|
|
154
|
+
await clickOnStep(1);
|
|
155
|
+
expect(clickedOnSecondStep).not.toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('are active when they are the currently active step', () => {
|
|
159
|
+
const { rerender } = customRender({
|
|
160
|
+
steps: Array(4)
|
|
161
|
+
.fill('')
|
|
162
|
+
.map((_, i) => ({ label: i.toString() })),
|
|
163
|
+
activeStep: 1,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const stepActive = (index: number) =>
|
|
167
|
+
getSteps()[index].classList.contains('tw-stepper__step--active');
|
|
168
|
+
|
|
169
|
+
expect(stepActive(0)).toBe(false);
|
|
170
|
+
expect(stepActive(1)).toBe(true);
|
|
171
|
+
expect(stepActive(2)).toBe(false);
|
|
172
|
+
expect(stepActive(3)).toBe(false);
|
|
173
|
+
|
|
174
|
+
rerender(<Stepper {...initialProps} activeStep={2} />);
|
|
175
|
+
|
|
176
|
+
expect(stepActive(1)).toBe(false);
|
|
177
|
+
expect(stepActive(2)).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('are aria-current=step when active', () => {
|
|
181
|
+
const { rerender } = customRender({ steps: Array(4).fill({ label: '' }), activeStep: 1 });
|
|
182
|
+
|
|
183
|
+
const stepCurrent = (index: number) =>
|
|
184
|
+
getSteps()[index].getAttribute('aria-current') === 'step';
|
|
185
|
+
|
|
186
|
+
expect(stepCurrent(0)).toBe(false);
|
|
187
|
+
expect(stepCurrent(1)).toBe(true);
|
|
188
|
+
expect(stepCurrent(2)).toBe(false);
|
|
189
|
+
expect(stepCurrent(3)).toBe(false);
|
|
190
|
+
|
|
191
|
+
rerender(<Stepper {...initialProps} activeStep={2} />);
|
|
192
|
+
|
|
193
|
+
expect(stepCurrent(1)).toBe(false);
|
|
194
|
+
expect(stepCurrent(2)).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('hover labels', () => {
|
|
199
|
+
it('will be rendered when provided', async () => {
|
|
200
|
+
const hoverLabel = 'hover label';
|
|
201
|
+
customRender({
|
|
202
|
+
steps: [{ hoverLabel, label: 'label' }, { label: 'label 2' }],
|
|
203
|
+
});
|
|
204
|
+
expect(await screen.findByText(hoverLabel)).toBeInTheDocument();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('renders jsx', async () => {
|
|
208
|
+
customRender({
|
|
209
|
+
steps: [
|
|
210
|
+
{
|
|
211
|
+
hoverLabel: (
|
|
212
|
+
<>
|
|
213
|
+
<span>hover label 1</span>
|
|
214
|
+
<p>hover label 2</p>
|
|
215
|
+
</>
|
|
216
|
+
),
|
|
217
|
+
label: 'one',
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(await screen.findByText('hover label 1')).toBeInTheDocument();
|
|
223
|
+
expect(await screen.findByText('hover label 2')).toBeInTheDocument();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('will not be rendered if the user is on a touch device', () => {
|
|
227
|
+
jest.spyOn(mockedDeviceDetection, 'isTouchDevice').mockImplementation(() => true);
|
|
228
|
+
|
|
229
|
+
customRender({
|
|
230
|
+
steps: [{ hoverLabel: 'hover label', label: 'label' }, { label: 'label 2' }],
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(screen.queryByText('hover label')).not.toBeInTheDocument();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|