@hero-design/rn-work-uikit 1.9.5 → 1.10.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/CHANGELOG.md +21 -0
- package/README.md +219 -63
- package/assets/fonts/hero-icons-mobile.ttf +0 -0
- package/es/index.js +55337 -0
- package/lib/index.js +7173 -21259
- package/package.json +22 -22
- package/src/components/FormGroup/index.tsx +21 -10
- package/src/components/FormGroup/utils.ts +3 -3
- package/src/components/TextInput/InputRow.tsx +3 -6
- package/src/components/TextInput/index.tsx +4 -4
- package/src/components/TextInput/types.ts +4 -8
- package/types/index.d.ts +525 -0
- package/src/__tests__/index-export.spec.ts +0 -64
- package/src/__tests__/index.spec.tsx +0 -14
- package/src/components/DatePicker/__tests__/__snapshots__/index.spec.tsx.snap +0 -1649
- package/src/components/DatePicker/__tests__/index.spec.tsx +0 -56
- package/src/components/FormGroup/__tests__/__snapshots__/index.spec.tsx.snap +0 -908
- package/src/components/FormGroup/__tests__/index.spec.tsx +0 -319
- package/src/components/FormGroup/__tests__/utils.spec.ts +0 -73
- package/src/components/RichTextEditor/__tests__/EditorToolbar.spec.tsx +0 -154
- package/src/components/RichTextEditor/__tests__/MentionList.spec.tsx +0 -105
- package/src/components/RichTextEditor/__tests__/RichTextEditor.spec.tsx +0 -81
- package/src/components/RichTextEditor/__tests__/RichTextEditorInput.spec.tsx +0 -174
- package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +0 -407
- package/src/components/RichTextEditor/__tests__/__snapshots__/MentionList.spec.tsx.snap +0 -13
- package/src/components/Select/__tests__/__snapshots__/index.spec.tsx.snap +0 -1324
- package/src/components/Select/__tests__/index.spec.tsx +0 -43
- package/src/components/TextInput/__tests__/ErrorOrHelpText.spec.tsx +0 -20
- package/src/components/TextInput/__tests__/FloatingLabel.spec.tsx +0 -190
- package/src/components/TextInput/__tests__/InputComponent.spec.tsx +0 -41
- package/src/components/TextInput/__tests__/InputRow.spec.tsx +0 -233
- package/src/components/TextInput/__tests__/MaxLengthMessage.spec.tsx +0 -17
- package/src/components/TextInput/__tests__/PrefixComponent.spec.tsx +0 -14
- package/src/components/TextInput/__tests__/StyledTextInput.spec.tsx +0 -114
- package/src/components/TextInput/__tests__/SuffixComponent.spec.tsx +0 -20
- package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +0 -583
- package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +0 -5835
- package/src/components/TextInput/__tests__/getState.spec.tsx +0 -89
- package/src/components/TextInput/__tests__/index.spec.tsx +0 -679
- package/src/components/TimePicker/__tests__/index.spec.tsx +0 -34
- package/src/theme/__tests__/ThemeProvider.spec.tsx +0 -32
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +0 -2042
- package/src/theme/__tests__/index.spec.ts +0 -7
- package/src/utils/__tests__/helpers.spec.ts +0 -92
- package/stats/1.3.0/rn-work-uikit-stats.html +0 -4842
|
@@ -1,679 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { act, fireEvent, waitFor, within } from '@testing-library/react-native';
|
|
3
|
-
import type {
|
|
4
|
-
TextInput as RNTextInput,
|
|
5
|
-
ViewStyle,
|
|
6
|
-
StyleProp,
|
|
7
|
-
} from 'react-native';
|
|
8
|
-
import { StyleSheet } from 'react-native';
|
|
9
|
-
import { Icon } from '@hero-design/rn';
|
|
10
|
-
import { theme } from '../../..';
|
|
11
|
-
import renderWithTheme from '../../../../testUtils/renderWithTheme';
|
|
12
|
-
import type { TextInputHandles } from '../index';
|
|
13
|
-
import TextInput from '../index';
|
|
14
|
-
|
|
15
|
-
describe('TextInput', () => {
|
|
16
|
-
describe('when user sees an empty input field', () => {
|
|
17
|
-
it('should display label and icons but hide input content until user interacts', () => {
|
|
18
|
-
const { toJSON, getByTestId, queryAllByTestId } = renderWithTheme(
|
|
19
|
-
<TextInput
|
|
20
|
-
label="Amount (AUD)"
|
|
21
|
-
prefix="dollar-sign"
|
|
22
|
-
suffix="arrow-down"
|
|
23
|
-
style={{ borderColor: theme.colors.error }}
|
|
24
|
-
/>
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const textInput = getByTestId('text-input');
|
|
28
|
-
expect(textInput).toBeTruthy();
|
|
29
|
-
|
|
30
|
-
// Ensure all visual elements are present
|
|
31
|
-
expect(queryAllByTestId('input-prefix')).toHaveLength(1);
|
|
32
|
-
expect(queryAllByTestId('input-label')).toHaveLength(1);
|
|
33
|
-
expect(queryAllByTestId('input-suffix')).toHaveLength(1);
|
|
34
|
-
|
|
35
|
-
// User should see the label text
|
|
36
|
-
const inputLabel = getByTestId('input-label');
|
|
37
|
-
expect(inputLabel).toHaveTextContent('Amount (AUD) (Optional)');
|
|
38
|
-
|
|
39
|
-
// User should be able to identify the prefix icon through accessibility
|
|
40
|
-
const prefixIcon = getByTestId('input-prefix-icon');
|
|
41
|
-
expect(prefixIcon).toBeTruthy();
|
|
42
|
-
expect(prefixIcon).toHaveProp(
|
|
43
|
-
'accessibilityLabel',
|
|
44
|
-
'Prefix icon: dollar-sign'
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
expect(toJSON()).toMatchSnapshot();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should not show label when no label text is provided', () => {
|
|
51
|
-
const { queryByTestId, queryAllByTestId } = renderWithTheme(
|
|
52
|
-
<TextInput
|
|
53
|
-
prefix="dollar-sign"
|
|
54
|
-
suffix="arrow-down"
|
|
55
|
-
testID="idle-text-input"
|
|
56
|
-
/>
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// User should not see any label element when none is provided
|
|
60
|
-
expect(queryAllByTestId('input-label')).toHaveLength(0);
|
|
61
|
-
|
|
62
|
-
// Input elements should still be present but hidden until interaction
|
|
63
|
-
expect(queryByTestId('text-input-idle-text-input')).toBeTruthy();
|
|
64
|
-
expect(queryByTestId('input-prefix')).toBeTruthy();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should respond to user interactions with proper callbacks', () => {
|
|
68
|
-
const onChangeText = jest.fn();
|
|
69
|
-
const onBlur = jest.fn();
|
|
70
|
-
const onFocus = jest.fn();
|
|
71
|
-
|
|
72
|
-
const { getByTestId } = renderWithTheme(
|
|
73
|
-
<TextInput
|
|
74
|
-
label="Amount (AUD)"
|
|
75
|
-
prefix="dollar-sign"
|
|
76
|
-
suffix="arrow-down"
|
|
77
|
-
onChangeText={onChangeText}
|
|
78
|
-
onBlur={onBlur}
|
|
79
|
-
onFocus={onFocus}
|
|
80
|
-
/>
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const textInput = getByTestId('text-input');
|
|
84
|
-
|
|
85
|
-
// User focuses the input
|
|
86
|
-
fireEvent(textInput, 'focus');
|
|
87
|
-
expect(onFocus).toHaveBeenCalledTimes(1);
|
|
88
|
-
|
|
89
|
-
// User types text into the input
|
|
90
|
-
fireEvent.changeText(textInput, 'Thong Quach');
|
|
91
|
-
expect(onChangeText).toHaveBeenCalledWith('Thong Quach');
|
|
92
|
-
|
|
93
|
-
// User moves focus away from the input
|
|
94
|
-
fireEvent(textInput, 'blur');
|
|
95
|
-
expect(onBlur).toHaveBeenCalledTimes(1);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('when user sees input with custom prefix and suffix elements', () => {
|
|
100
|
-
it('should display custom React components instead of icon names', () => {
|
|
101
|
-
const { toJSON, getByTestId } = renderWithTheme(
|
|
102
|
-
<TextInput
|
|
103
|
-
label="Amount (AUD)"
|
|
104
|
-
prefix={<Icon icon="eye-circle" testID="prefix-element" />}
|
|
105
|
-
suffix={<Icon icon="eye-invisible" testID="suffix-element" />}
|
|
106
|
-
required
|
|
107
|
-
/>
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
// User should see the label with an asterisk
|
|
111
|
-
const textInput = getByTestId('text-input');
|
|
112
|
-
expect(textInput).toBeTruthy();
|
|
113
|
-
|
|
114
|
-
// User should see all visual elements including custom components
|
|
115
|
-
expect(getByTestId('prefix-element')).toBeTruthy();
|
|
116
|
-
expect(getByTestId('suffix-element')).toBeTruthy();
|
|
117
|
-
|
|
118
|
-
expect(toJSON()).toMatchSnapshot();
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('when user sees a required field', () => {
|
|
123
|
-
it('should indicate the field is required through styling', () => {
|
|
124
|
-
const { toJSON, getByTestId } = renderWithTheme(
|
|
125
|
-
<TextInput
|
|
126
|
-
label="Amount (AUD)"
|
|
127
|
-
prefix="dollar-sign"
|
|
128
|
-
suffix="arrow-down"
|
|
129
|
-
required
|
|
130
|
-
/>
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
// User should see the label text with an asterisk
|
|
134
|
-
const textInput = getByTestId('text-input');
|
|
135
|
-
expect(textInput).toBeTruthy();
|
|
136
|
-
|
|
137
|
-
expect(toJSON()).toMatchSnapshot();
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('when user has entered text', () => {
|
|
142
|
-
it('should show the input content and maintain all visual elements', () => {
|
|
143
|
-
const { toJSON, getByDisplayValue } = renderWithTheme(
|
|
144
|
-
<TextInput
|
|
145
|
-
label="Amount (AUD)"
|
|
146
|
-
prefix="dollar-sign"
|
|
147
|
-
suffix="arrow-down"
|
|
148
|
-
value="100"
|
|
149
|
-
/>
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
// User should see their entered value
|
|
153
|
-
const textInput = getByDisplayValue('100');
|
|
154
|
-
expect(textInput).toBeTruthy();
|
|
155
|
-
|
|
156
|
-
expect(toJSON()).toMatchSnapshot();
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe('when label is not provided', () => {
|
|
161
|
-
it('should show the input content', () => {
|
|
162
|
-
const { getByDisplayValue } = renderWithTheme(
|
|
163
|
-
<TextInput value="This is the content" />
|
|
164
|
-
);
|
|
165
|
-
expect(getByDisplayValue('This is the content')).toBeTruthy();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('when user encounters a read-only field', () => {
|
|
170
|
-
it('should display content but prevent user from editing', () => {
|
|
171
|
-
const onChangeText = jest.fn();
|
|
172
|
-
const onFocus = jest.fn();
|
|
173
|
-
const { toJSON, getByDisplayValue, getByTestId } = renderWithTheme(
|
|
174
|
-
<TextInput
|
|
175
|
-
label="Amount (AUD)"
|
|
176
|
-
prefix="dollar-sign"
|
|
177
|
-
suffix="arrow-down"
|
|
178
|
-
editable={false}
|
|
179
|
-
value="Read-only value"
|
|
180
|
-
onChangeText={onChangeText}
|
|
181
|
-
onFocus={onFocus}
|
|
182
|
-
/>
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
// User should see the content and label
|
|
186
|
-
const textInput = getByTestId('text-input');
|
|
187
|
-
expect(textInput).toBeTruthy();
|
|
188
|
-
expect(getByDisplayValue('Read-only value')).toBeTruthy();
|
|
189
|
-
|
|
190
|
-
// The input should have editable=false prop
|
|
191
|
-
expect(textInput).toHaveProp('editable', false);
|
|
192
|
-
|
|
193
|
-
// Simulate user trying to type
|
|
194
|
-
fireEvent.changeText(textInput, 'New value');
|
|
195
|
-
expect(onChangeText).not.toHaveBeenCalled();
|
|
196
|
-
|
|
197
|
-
// It should still show the old value
|
|
198
|
-
expect(textInput.props.value).toBe('Read-only value');
|
|
199
|
-
|
|
200
|
-
// User should still see the original value displayed
|
|
201
|
-
expect(getByDisplayValue('Read-only value')).toBeTruthy();
|
|
202
|
-
|
|
203
|
-
expect(toJSON()).toMatchSnapshot();
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('when user sees a loading state', () => {
|
|
208
|
-
it('should show loading indicator in place of suffix icon', () => {
|
|
209
|
-
const { toJSON, getByTestId } = renderWithTheme(
|
|
210
|
-
<TextInput
|
|
211
|
-
label="Amount (AUD)"
|
|
212
|
-
prefix="dollar-sign"
|
|
213
|
-
suffix="arrow-down"
|
|
214
|
-
loading
|
|
215
|
-
value="100"
|
|
216
|
-
/>
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
const textInput = getByTestId('text-input');
|
|
220
|
-
expect(textInput).toBeTruthy();
|
|
221
|
-
|
|
222
|
-
// User should see loading indicator instead of regular suffix
|
|
223
|
-
const suffixContainer = getByTestId('input-suffix');
|
|
224
|
-
expect(suffixContainer).toBeTruthy();
|
|
225
|
-
|
|
226
|
-
const loadingIcon = within(suffixContainer).getByLabelText(
|
|
227
|
-
'Suffix icon: loading'
|
|
228
|
-
);
|
|
229
|
-
expect(loadingIcon).toBeTruthy();
|
|
230
|
-
|
|
231
|
-
expect(toJSON()).toMatchSnapshot();
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe('when user sees a textarea with character count', () => {
|
|
236
|
-
it('should display multiline input with character counter', () => {
|
|
237
|
-
const { toJSON, getByTestId, getByText } = renderWithTheme(
|
|
238
|
-
<TextInput
|
|
239
|
-
label="Amount (AUD)"
|
|
240
|
-
prefix="dollar-sign"
|
|
241
|
-
suffix="arrow-down"
|
|
242
|
-
value="100"
|
|
243
|
-
maxLength={255}
|
|
244
|
-
variant="textarea"
|
|
245
|
-
/>
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
// User should see the label and character count
|
|
249
|
-
const textInput = getByTestId('text-input');
|
|
250
|
-
expect(textInput).toBeTruthy();
|
|
251
|
-
expect(getByText('3/255')).toBeTruthy();
|
|
252
|
-
|
|
253
|
-
// User should be able to enter multiple lines
|
|
254
|
-
expect(textInput).toHaveProp('multiline', true);
|
|
255
|
-
|
|
256
|
-
expect(toJSON()).toMatchSnapshot();
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('should hide character count when user requests it', () => {
|
|
260
|
-
const { toJSON, queryByText, getByTestId } = renderWithTheme(
|
|
261
|
-
<TextInput
|
|
262
|
-
label="Amount (AUD)"
|
|
263
|
-
prefix="dollar-sign"
|
|
264
|
-
suffix="arrow-down"
|
|
265
|
-
value="100"
|
|
266
|
-
maxLength={255}
|
|
267
|
-
hideCharacterCount
|
|
268
|
-
variant="textarea"
|
|
269
|
-
/>
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
// User should see the label but not the character count
|
|
273
|
-
const textInput = getByTestId('text-input');
|
|
274
|
-
expect(textInput).toBeTruthy();
|
|
275
|
-
expect(queryByText('3/255')).toBeFalsy();
|
|
276
|
-
|
|
277
|
-
// User should still be able to enter multiple lines
|
|
278
|
-
expect(textInput).toHaveProp('multiline', true);
|
|
279
|
-
|
|
280
|
-
expect(toJSON()).toMatchSnapshot();
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
describe('when user encounters a disabled field', () => {
|
|
285
|
-
it('should display content but prevent any user interaction', () => {
|
|
286
|
-
const { toJSON, getByTestId } = renderWithTheme(
|
|
287
|
-
<TextInput label="Amount (AUD)" disabled value="100" />
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
const textInput = getByTestId('text-input');
|
|
291
|
-
expect(textInput).toBeDisabled();
|
|
292
|
-
|
|
293
|
-
// Visual styling should indicate disabled state
|
|
294
|
-
const borderElement = getByTestId('text-input-border');
|
|
295
|
-
expect(borderElement).toBeTruthy();
|
|
296
|
-
|
|
297
|
-
// Helper function to extract border color from complex React Native styles
|
|
298
|
-
function getBorderColor(style: unknown): string | undefined {
|
|
299
|
-
if (Array.isArray(style)) {
|
|
300
|
-
return style.reduce<string | undefined>((result, styleItem) => {
|
|
301
|
-
if (result) return result;
|
|
302
|
-
return getBorderColor(styleItem);
|
|
303
|
-
}, undefined);
|
|
304
|
-
}
|
|
305
|
-
if (style && typeof style === 'object') {
|
|
306
|
-
const flattened = StyleSheet.flatten(style as StyleProp<ViewStyle>);
|
|
307
|
-
return flattened?.borderColor as string | undefined;
|
|
308
|
-
}
|
|
309
|
-
return undefined;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// User should see disabled styling (muted border color)
|
|
313
|
-
const borderColor = getBorderColor(borderElement.props.style);
|
|
314
|
-
expect(borderColor).toBe('#bfc1c5');
|
|
315
|
-
|
|
316
|
-
expect(toJSON()).toMatchSnapshot();
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe('when user sees an error state', () => {
|
|
321
|
-
it('should display error message to help user understand the issue', () => {
|
|
322
|
-
const { toJSON, getByText } = renderWithTheme(
|
|
323
|
-
<TextInput
|
|
324
|
-
label="Amount (AUD)"
|
|
325
|
-
prefix="dollar-sign"
|
|
326
|
-
required
|
|
327
|
-
error="This field is required"
|
|
328
|
-
/>
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
// User should see both the label and error message
|
|
332
|
-
expect(getByText('This field is required')).toBeTruthy();
|
|
333
|
-
|
|
334
|
-
expect(toJSON()).toMatchSnapshot();
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
describe('when user sees helper text', () => {
|
|
339
|
-
it('should display guidance text to assist user understanding', () => {
|
|
340
|
-
const { toJSON, getByText } = renderWithTheme(
|
|
341
|
-
<TextInput
|
|
342
|
-
label="Amount (AUD)"
|
|
343
|
-
prefix="dollar-sign"
|
|
344
|
-
required
|
|
345
|
-
helpText="This is helper text"
|
|
346
|
-
/>
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
// User should see both the label and helpful guidance
|
|
350
|
-
expect(getByText('This is helper text')).toBeTruthy();
|
|
351
|
-
|
|
352
|
-
expect(toJSON()).toMatchSnapshot();
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
describe('when user interacts with placeholder text', () => {
|
|
357
|
-
describe('starting from empty field', () => {
|
|
358
|
-
it('should show placeholder when user focuses, hide when user leaves empty', async () => {
|
|
359
|
-
const { getByTestId, queryByPlaceholderText, toJSON } = renderWithTheme(
|
|
360
|
-
<TextInput
|
|
361
|
-
label="Amount (AUD)"
|
|
362
|
-
prefix="dollar-sign"
|
|
363
|
-
required
|
|
364
|
-
helpText="This is helper text"
|
|
365
|
-
placeholder="Enter Amount"
|
|
366
|
-
/>
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
const textInput = getByTestId('text-input');
|
|
370
|
-
expect(textInput).toBeTruthy();
|
|
371
|
-
|
|
372
|
-
// User focuses the input field
|
|
373
|
-
fireEvent(textInput, 'focus');
|
|
374
|
-
await waitFor(() => {
|
|
375
|
-
expect(queryByPlaceholderText('Enter Amount')).toBeTruthy();
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
// User leaves the field empty and moves focus away
|
|
379
|
-
fireEvent(textInput, 'blur');
|
|
380
|
-
await waitFor(() => {
|
|
381
|
-
expect(textInput).toHaveProp('placeholder', ' ');
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// Input remains empty, so placeholder should be hidden again
|
|
385
|
-
expect(queryByPlaceholderText('Placeholder')).toBeFalsy();
|
|
386
|
-
|
|
387
|
-
expect(toJSON()).toMatchSnapshot();
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
describe('when user provides default values', () => {
|
|
393
|
-
describe('starting with pre-filled content', () => {
|
|
394
|
-
it('should display default value and character count', () => {
|
|
395
|
-
const { getByDisplayValue, getByText, toJSON } = renderWithTheme(
|
|
396
|
-
<TextInput
|
|
397
|
-
label="Amount (AUD)"
|
|
398
|
-
prefix="dollar-sign"
|
|
399
|
-
required
|
|
400
|
-
helpText="This is helper text"
|
|
401
|
-
placeholder="Enter Amount"
|
|
402
|
-
defaultValue="1000"
|
|
403
|
-
maxLength={255}
|
|
404
|
-
/>
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
// User should see the pre-filled value
|
|
408
|
-
expect(getByDisplayValue('1000')).toBeTruthy();
|
|
409
|
-
|
|
410
|
-
// User should see character count reflecting the default value
|
|
411
|
-
expect(getByText('4/255')).toBeTruthy();
|
|
412
|
-
|
|
413
|
-
expect(toJSON()).toMatchSnapshot();
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
describe('when both default and controlled values are provided', () => {
|
|
418
|
-
it('should prioritize controlled value over default value', () => {
|
|
419
|
-
const { getByDisplayValue, queryByDisplayValue, getByText, toJSON } =
|
|
420
|
-
renderWithTheme(
|
|
421
|
-
<TextInput
|
|
422
|
-
label="Amount (AUD)"
|
|
423
|
-
prefix="dollar-sign"
|
|
424
|
-
required
|
|
425
|
-
helpText="This is helper text"
|
|
426
|
-
placeholder="Enter Amount"
|
|
427
|
-
defaultValue="1000"
|
|
428
|
-
value="2000"
|
|
429
|
-
maxLength={255}
|
|
430
|
-
/>
|
|
431
|
-
);
|
|
432
|
-
|
|
433
|
-
// User should see the controlled value, not the default
|
|
434
|
-
expect(getByDisplayValue('2000')).toBeVisible();
|
|
435
|
-
expect(queryByDisplayValue('1000')).toBeFalsy();
|
|
436
|
-
|
|
437
|
-
// Character count should reflect the actual displayed value
|
|
438
|
-
expect(getByText('4/255')).toBeTruthy();
|
|
439
|
-
|
|
440
|
-
expect(toJSON()).toMatchSnapshot();
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
describe('when user applies custom styling', () => {
|
|
446
|
-
it('should respect user-provided background color styling', () => {
|
|
447
|
-
const { getByTestId, toJSON } = renderWithTheme(
|
|
448
|
-
<TextInput
|
|
449
|
-
label="Amount (AUD)"
|
|
450
|
-
prefix="dollar-sign"
|
|
451
|
-
required
|
|
452
|
-
helpText="This is helper text"
|
|
453
|
-
placeholder="Enter Amount"
|
|
454
|
-
defaultValue="1000"
|
|
455
|
-
value="2000"
|
|
456
|
-
maxLength={255}
|
|
457
|
-
style={{ backgroundColor: 'customColor' }}
|
|
458
|
-
/>
|
|
459
|
-
);
|
|
460
|
-
|
|
461
|
-
const textInput = getByTestId('text-input');
|
|
462
|
-
|
|
463
|
-
// Helper function to extract background color from complex React Native styles
|
|
464
|
-
function getBackgroundColor(
|
|
465
|
-
style: StyleProp<ViewStyle> | unknown[]
|
|
466
|
-
): string | undefined {
|
|
467
|
-
function flatten(s: StyleProp<ViewStyle> | unknown[]): ViewStyle[] {
|
|
468
|
-
if (Array.isArray(s)) {
|
|
469
|
-
return s
|
|
470
|
-
.filter(Boolean)
|
|
471
|
-
.reduce<ViewStyle[]>(
|
|
472
|
-
(acc, item) =>
|
|
473
|
-
acc.concat(flatten(item as StyleProp<ViewStyle> | unknown[])),
|
|
474
|
-
[]
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
if (s) {
|
|
478
|
-
return [StyleSheet.flatten(s as StyleProp<ViewStyle>) as ViewStyle];
|
|
479
|
-
}
|
|
480
|
-
return [];
|
|
481
|
-
}
|
|
482
|
-
const flat = flatten(style);
|
|
483
|
-
for (let i = flat.length - 1; i >= 0; i -= 1) {
|
|
484
|
-
if (flat[i].backgroundColor !== undefined) {
|
|
485
|
-
return flat[i].backgroundColor as string;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return undefined;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// User should see their custom background color applied
|
|
492
|
-
const textInputStyle = textInput.props.style;
|
|
493
|
-
expect(getBackgroundColor(textInputStyle)).toBe('customColor');
|
|
494
|
-
|
|
495
|
-
const borderStyle = getByTestId('text-input-border').props.style;
|
|
496
|
-
expect(getBackgroundColor(borderStyle)).toBe('customColor');
|
|
497
|
-
|
|
498
|
-
expect(toJSON()).toMatchSnapshot();
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
describe('when user needs programmatic control', () => {
|
|
503
|
-
it('should provide ref methods for external control of the input', () => {
|
|
504
|
-
const ref = React.createRef<
|
|
505
|
-
TextInputHandles & {
|
|
506
|
-
getNativeTextInputRef(): RNTextInput;
|
|
507
|
-
}
|
|
508
|
-
>();
|
|
509
|
-
|
|
510
|
-
const { getByDisplayValue, toJSON } = renderWithTheme(
|
|
511
|
-
<TextInput label="Amount (AUD)" value="2000" ref={ref} />
|
|
512
|
-
);
|
|
513
|
-
|
|
514
|
-
// User should see the initial value
|
|
515
|
-
expect(getByDisplayValue('2000')).toBeTruthy();
|
|
516
|
-
|
|
517
|
-
// External code should be able to control the input programmatically
|
|
518
|
-
const nativeTextInputRef = ref.current?.getNativeTextInputRef();
|
|
519
|
-
if (nativeTextInputRef && ref.current) {
|
|
520
|
-
const focusSpy = jest.spyOn(nativeTextInputRef, 'focus');
|
|
521
|
-
const clearSpy = jest.spyOn(nativeTextInputRef, 'clear');
|
|
522
|
-
const blurSpy = jest.spyOn(nativeTextInputRef, 'blur');
|
|
523
|
-
const setNativePropsSpy = jest.spyOn(
|
|
524
|
-
nativeTextInputRef,
|
|
525
|
-
'setNativeProps'
|
|
526
|
-
);
|
|
527
|
-
|
|
528
|
-
act(() => {
|
|
529
|
-
ref.current?.focus();
|
|
530
|
-
ref.current?.clear();
|
|
531
|
-
ref.current?.setNativeProps({ text: '1000' });
|
|
532
|
-
ref.current?.blur();
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
// Verify that programmatic methods work as expected
|
|
536
|
-
expect(focusSpy).toHaveBeenCalledTimes(1);
|
|
537
|
-
expect(clearSpy).toHaveBeenCalledTimes(1);
|
|
538
|
-
expect(setNativePropsSpy).toHaveBeenCalledTimes(1);
|
|
539
|
-
expect(blurSpy).toHaveBeenCalledTimes(1);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
expect(toJSON()).toMatchSnapshot();
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
describe('when user chooses textarea variant', () => {
|
|
547
|
-
it('should provide multiline text input experience', () => {
|
|
548
|
-
const { toJSON, getByDisplayValue } = renderWithTheme(
|
|
549
|
-
<TextInput label="Amount (AUD)" value="2000" variant="textarea" />
|
|
550
|
-
);
|
|
551
|
-
|
|
552
|
-
// User should see their entered value
|
|
553
|
-
expect(getByDisplayValue('2000')).toBeTruthy();
|
|
554
|
-
|
|
555
|
-
expect(toJSON()).toMatchSnapshot();
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
describe('when user encounters an error state that needs to be corrected', () => {
|
|
560
|
-
it('should allow focusing input even when error is present', () => {
|
|
561
|
-
const onFocus = jest.fn();
|
|
562
|
-
const { getByTestId } = renderWithTheme(
|
|
563
|
-
<TextInput label="Email" error="Invalid Email" onFocus={onFocus} />
|
|
564
|
-
);
|
|
565
|
-
|
|
566
|
-
// User should be able to focus the input despite the error
|
|
567
|
-
const textInput = getByTestId('text-input');
|
|
568
|
-
fireEvent(textInput, 'focus');
|
|
569
|
-
|
|
570
|
-
// Should trigger focus callback
|
|
571
|
-
expect(onFocus).toHaveBeenCalled();
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
it('should display focused styling when error input is focused', async () => {
|
|
575
|
-
const { getByTestId } = renderWithTheme(
|
|
576
|
-
<TextInput label="Email" error="Invalid Email" />
|
|
577
|
-
);
|
|
578
|
-
|
|
579
|
-
// Focus the input
|
|
580
|
-
const textInput = getByTestId('text-input');
|
|
581
|
-
fireEvent(textInput, 'focus');
|
|
582
|
-
|
|
583
|
-
await waitFor(() => {
|
|
584
|
-
const border = getByTestId('text-input-border');
|
|
585
|
-
expect(border).toHaveProp('themeFocused', true);
|
|
586
|
-
expect(border).toHaveProp('themeState', 'error');
|
|
587
|
-
});
|
|
588
|
-
});
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
describe('when text is changed in the input', () => {
|
|
592
|
-
it('updates the display value', () => {
|
|
593
|
-
const ControllableInput = () => {
|
|
594
|
-
const [value, setValue] = React.useState('');
|
|
595
|
-
return (
|
|
596
|
-
<TextInput
|
|
597
|
-
label="Amount (AUD)"
|
|
598
|
-
value={value}
|
|
599
|
-
onChangeText={setValue}
|
|
600
|
-
/>
|
|
601
|
-
);
|
|
602
|
-
};
|
|
603
|
-
const { getByTestId, getByDisplayValue } = renderWithTheme(
|
|
604
|
-
<ControllableInput />
|
|
605
|
-
);
|
|
606
|
-
|
|
607
|
-
fireEvent.changeText(getByTestId('text-input'), '100');
|
|
608
|
-
expect(getByDisplayValue('100')).toBeTruthy();
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
it('does not update when not editable', () => {
|
|
612
|
-
const ControllableInput = () => {
|
|
613
|
-
const [value, setValue] = React.useState('hello');
|
|
614
|
-
return (
|
|
615
|
-
<TextInput
|
|
616
|
-
label="Amount (AUD)"
|
|
617
|
-
value={value}
|
|
618
|
-
onChangeText={setValue}
|
|
619
|
-
editable={false}
|
|
620
|
-
/>
|
|
621
|
-
);
|
|
622
|
-
};
|
|
623
|
-
const { getByTestId, getByDisplayValue } = renderWithTheme(
|
|
624
|
-
<ControllableInput />
|
|
625
|
-
);
|
|
626
|
-
|
|
627
|
-
// This should not work
|
|
628
|
-
fireEvent.changeText(getByTestId('text-input'), 'new value');
|
|
629
|
-
|
|
630
|
-
// show old value
|
|
631
|
-
expect(getByDisplayValue('hello')).toBeTruthy();
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
it('allows user to press label to focus and type', () => {
|
|
635
|
-
const ControllableInput = () => {
|
|
636
|
-
const [value, setValue] = React.useState('');
|
|
637
|
-
return (
|
|
638
|
-
<TextInput label="My Label" value={value} onChangeText={setValue} />
|
|
639
|
-
);
|
|
640
|
-
};
|
|
641
|
-
const { getByText, getByDisplayValue, getByTestId } = renderWithTheme(
|
|
642
|
-
<ControllableInput />
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
// Pressing the label should focus the input
|
|
646
|
-
fireEvent.press(getByText('My Label'));
|
|
647
|
-
|
|
648
|
-
// Now we can type into the focused input
|
|
649
|
-
fireEvent.changeText(getByTestId('text-input'), 'hello');
|
|
650
|
-
|
|
651
|
-
// Verify the value has changed
|
|
652
|
-
expect(getByDisplayValue('hello')).toBeVisible();
|
|
653
|
-
});
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
it('should allow user to type text', () => {
|
|
657
|
-
const handleChangeText = jest.fn();
|
|
658
|
-
const { getByTestId } = renderWithTheme(
|
|
659
|
-
<TextInput
|
|
660
|
-
label="Email"
|
|
661
|
-
onChangeText={handleChangeText}
|
|
662
|
-
testID="email-input-container"
|
|
663
|
-
/>
|
|
664
|
-
);
|
|
665
|
-
|
|
666
|
-
// 1. Get both the container and the actual input field
|
|
667
|
-
const container = getByTestId('email-input-container');
|
|
668
|
-
const textInput = getByTestId('text-input-email-input-container');
|
|
669
|
-
|
|
670
|
-
// 2. Focus the component by interacting with the container
|
|
671
|
-
fireEvent(container, 'focus');
|
|
672
|
-
|
|
673
|
-
// 3. Type text by firing changeText on the input field itself
|
|
674
|
-
fireEvent.changeText(textInput, 'hello world');
|
|
675
|
-
|
|
676
|
-
// 4. Assert that your onChangeText handler was called with the correct text
|
|
677
|
-
expect(handleChangeText).toHaveBeenCalledWith('hello world');
|
|
678
|
-
});
|
|
679
|
-
});
|