@react-native-ama/forms 1.0.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/dist/core/src/components/AMAProvider.d.ts +26 -0
- package/dist/core/src/components/AMAProvider.js +147 -0
- package/dist/core/src/components/AutofocusContainer.d.ts +9 -0
- package/dist/core/src/components/AutofocusContainer.js +58 -0
- package/dist/core/src/components/HideChildrenFromAccessibilityTree.d.ts +6 -0
- package/dist/core/src/components/HideChildrenFromAccessibilityTree.js +40 -0
- package/dist/core/src/hooks/useButtonChecks.d.ts +6 -0
- package/dist/core/src/hooks/useButtonChecks.js +51 -0
- package/dist/core/src/hooks/useChecks.d.ts +15 -0
- package/dist/core/src/hooks/useChecks.js +152 -0
- package/dist/core/src/hooks/useFocus.d.ts +4 -0
- package/dist/core/src/hooks/useFocus.js +55 -0
- package/dist/core/src/hooks/useTimedAction.d.ts +3 -0
- package/dist/core/src/hooks/useTimedAction.js +63 -0
- package/dist/core/src/index.d.ts +7 -0
- package/dist/core/src/index.js +20 -0
- package/dist/forms/src/components/Form.d.ts +24 -0
- package/dist/forms/src/components/Form.js +122 -0
- package/dist/forms/src/components/FormField.d.ts +7 -0
- package/dist/forms/src/components/FormField.js +52 -0
- package/dist/forms/src/components/FormSubmit.d.ts +10 -0
- package/dist/forms/src/components/FormSubmit.js +25 -0
- package/dist/forms/src/components/FormSwitch.d.ts +4 -0
- package/dist/forms/src/components/FormSwitch.js +15 -0
- package/dist/forms/src/components/TextInput.d.ts +26 -0
- package/dist/forms/src/components/TextInput.js +123 -0
- package/dist/forms/src/hooks/useFormField.d.ts +26 -0
- package/dist/forms/src/hooks/useFormField.js +92 -0
- package/dist/forms/src/hooks/useTextInput.d.ts +350 -0
- package/dist/forms/src/hooks/useTextInput.js +102 -0
- package/dist/forms/src/index.d.ts +16 -0
- package/dist/forms/src/index.js +24 -0
- package/dist/internal/src/checks/checkAccessibilityRole.d.ts +3 -0
- package/dist/internal/src/checks/checkAccessibilityRole.js +32 -0
- package/dist/internal/src/checks/checkFocusTrap.d.ts +8 -0
- package/dist/internal/src/checks/checkFocusTrap.js +34 -0
- package/dist/internal/src/checks/checkForAccessibilityState.d.ts +7 -0
- package/dist/internal/src/checks/checkForAccessibilityState.js +48 -0
- package/dist/internal/src/checks/checkMinimumSize.d.ts +3 -0
- package/dist/internal/src/checks/checkMinimumSize.js +29 -0
- package/dist/internal/src/checks/contrastChecker.d.ts +8 -0
- package/dist/internal/src/checks/contrastChecker.js +77 -0
- package/dist/internal/src/checks/noUndefinedProperty.d.ts +8 -0
- package/dist/internal/src/checks/noUndefinedProperty.js +14 -0
- package/dist/internal/src/checks/uppercaseChecker.d.ts +8 -0
- package/dist/internal/src/checks/uppercaseChecker.js +18 -0
- package/dist/internal/src/checks/uppercaseStringChecker.d.ts +8 -0
- package/dist/internal/src/checks/uppercaseStringChecker.js +29 -0
- package/dist/internal/src/index.d.ts +20 -0
- package/dist/internal/src/index.js +56 -0
- package/dist/internal/src/types.d.ts +17 -0
- package/dist/internal/src/types.js +2 -0
- package/dist/internal/src/utils/applyStyle.d.ts +9 -0
- package/dist/internal/src/utils/applyStyle.js +33 -0
- package/dist/internal/src/utils/constants.d.ts +3 -0
- package/dist/internal/src/utils/constants.js +32 -0
- package/dist/internal/src/utils/error.style.d.ts +6 -0
- package/dist/internal/src/utils/error.style.js +11 -0
- package/dist/internal/src/utils/generateAccessibilityStateFromProp.d.ts +9 -0
- package/dist/internal/src/utils/generateAccessibilityStateFromProp.js +35 -0
- package/dist/internal/src/utils/getPropertyFromStyle.d.ts +2 -0
- package/dist/internal/src/utils/getPropertyFromStyle.js +10 -0
- package/dist/internal/src/utils/interpolateAnimationStates.d.ts +2 -0
- package/dist/internal/src/utils/interpolateAnimationStates.js +43 -0
- package/dist/internal/src/utils/isFocused.d.ts +2 -0
- package/dist/internal/src/utils/isFocused.js +7 -0
- package/dist/internal/src/utils/logger.d.ts +16 -0
- package/dist/internal/src/utils/logger.js +53 -0
- package/dist/internal/src/utils/logger.rules.d.ts +15 -0
- package/dist/internal/src/utils/logger.rules.js +74 -0
- package/dist/internal/src/utils/maybeGenerateStringFromElement.d.ts +2 -0
- package/dist/internal/src/utils/maybeGenerateStringFromElement.js +51 -0
- package/dist/internal/src/utils/minimumTouchableSize.d.ts +3 -0
- package/dist/internal/src/utils/minimumTouchableSize.js +9 -0
- package/dist/react-native/src/components/ExpandablePressable.d.ts +6 -0
- package/dist/react-native/src/components/ExpandablePressable.js +26 -0
- package/dist/react-native/src/components/Pressable.d.ts +5 -0
- package/dist/react-native/src/components/Pressable.js +49 -0
- package/dist/react-native/src/components/SwitchListItem.d.ts +12 -0
- package/dist/react-native/src/components/SwitchListItem.js +44 -0
- package/dist/react-native/src/components/SwitchWrapper.d.ts +11 -0
- package/dist/react-native/src/components/SwitchWrapper.js +36 -0
- package/dist/react-native/src/components/Text.d.ts +6 -0
- package/dist/react-native/src/components/Text.js +77 -0
- package/dist/react-native/src/components/TouchableOpacity.d.ts +6 -0
- package/dist/react-native/src/components/TouchableOpacity.js +48 -0
- package/dist/react-native/src/components/TouchableWithoutFeedback.d.ts +5 -0
- package/dist/react-native/src/components/TouchableWithoutFeedback.js +48 -0
- package/dist/react-native/src/hooks/useExpandable.d.ts +13 -0
- package/dist/react-native/src/hooks/useExpandable.js +31 -0
- package/dist/react-native/src/hooks/usePressable.d.ts +16 -0
- package/dist/react-native/src/hooks/usePressable.js +44 -0
- package/dist/react-native/src/hooks/useSwitch.d.ts +10 -0
- package/dist/react-native/src/hooks/useSwitch.js +25 -0
- package/dist/react-native/src/index.d.ts +10 -0
- package/dist/react-native/src/index.js +25 -0
- package/package.json +82 -0
- package/src/components/Form.test.tsx +262 -0
- package/src/components/Form.tsx +138 -0
- package/src/components/FormField.test.tsx +54 -0
- package/src/components/FormField.tsx +45 -0
- package/src/components/FormSubmit.test.tsx +71 -0
- package/src/components/FormSubmit.tsx +43 -0
- package/src/components/FormSwitch.tsx +17 -0
- package/src/components/TextInput.test.tsx +540 -0
- package/src/components/TextInput.tsx +168 -0
- package/src/hooks/useFormField.test.tsx +183 -0
- package/src/hooks/useFormField.ts +133 -0
- package/src/hooks/useTextInput.ts +128 -0
- package/src/index.ts +39 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import * as UseChecks from '@react-native-ama/core/src/hooks/useChecks';
|
|
2
|
+
import { ERROR_STYLE } from '@react-native-ama/internal';
|
|
3
|
+
import { fireEvent, render } from '@testing-library/react-native';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { Text } from 'react-native';
|
|
6
|
+
|
|
7
|
+
import * as UseFormField from '../hooks/useFormField';
|
|
8
|
+
import { TextInput } from './TextInput';
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('TextInput', () => {
|
|
15
|
+
it('register itself as form field by using the useFormField hook', () => {
|
|
16
|
+
const useFormField = jest.spyOn(UseFormField, 'useFormField');
|
|
17
|
+
|
|
18
|
+
render(
|
|
19
|
+
<TextInput
|
|
20
|
+
labelComponent={<Text testID="text">Test</Text>}
|
|
21
|
+
hasValidation={false}
|
|
22
|
+
returnKeyType="done"
|
|
23
|
+
/>,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(useFormField.mock.calls[0][0]).toMatchObject({
|
|
27
|
+
ref: expect.objectContaining({ current: expect.any(Object) }),
|
|
28
|
+
errorMessage: undefined,
|
|
29
|
+
hasError: undefined,
|
|
30
|
+
hasValidation: false,
|
|
31
|
+
id: undefined,
|
|
32
|
+
nextFieldId: undefined,
|
|
33
|
+
nextFormFieldRef: undefined,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders the given labelComponent before the text input when labelPosition is undefined', () => {
|
|
38
|
+
const renderAPI = render(
|
|
39
|
+
<TextInput
|
|
40
|
+
labelComponent={<Text testID="text">labelComponent</Text>}
|
|
41
|
+
returnKeyType="done"
|
|
42
|
+
hasValidation={false}
|
|
43
|
+
/>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(renderAPI.toJSON()).toMatchInlineSnapshot(`
|
|
47
|
+
Array [
|
|
48
|
+
<Text
|
|
49
|
+
accessibilityElementsHidden={true}
|
|
50
|
+
importantForAccessibility="no"
|
|
51
|
+
testID="text"
|
|
52
|
+
>
|
|
53
|
+
labelComponent
|
|
54
|
+
</Text>,
|
|
55
|
+
<TextInput
|
|
56
|
+
accessibilityHint=""
|
|
57
|
+
accessibilityLabel="labelComponent"
|
|
58
|
+
hasValidation={false}
|
|
59
|
+
onLayout={[Function]}
|
|
60
|
+
onSubmitEditing={[Function]}
|
|
61
|
+
returnKeyType="done"
|
|
62
|
+
style={Object {}}
|
|
63
|
+
/>,
|
|
64
|
+
]
|
|
65
|
+
`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders the given labelComponent after the text input when labelPosition is "afterInput"', () => {
|
|
69
|
+
const renderAPI = render(
|
|
70
|
+
<TextInput
|
|
71
|
+
labelComponent={<Text testID="text"> labelComponent after</Text>}
|
|
72
|
+
labelPosition="afterInput"
|
|
73
|
+
returnKeyType="done"
|
|
74
|
+
hasValidation={false}
|
|
75
|
+
/>,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(renderAPI.toJSON()).toMatchInlineSnapshot(`
|
|
79
|
+
Array [
|
|
80
|
+
<TextInput
|
|
81
|
+
accessibilityHint=""
|
|
82
|
+
accessibilityLabel=" labelComponent after"
|
|
83
|
+
hasValidation={false}
|
|
84
|
+
onLayout={[Function]}
|
|
85
|
+
onSubmitEditing={[Function]}
|
|
86
|
+
returnKeyType="done"
|
|
87
|
+
style={Object {}}
|
|
88
|
+
/>,
|
|
89
|
+
<Text
|
|
90
|
+
accessibilityElementsHidden={true}
|
|
91
|
+
importantForAccessibility="no"
|
|
92
|
+
testID="text"
|
|
93
|
+
>
|
|
94
|
+
labelComponent after
|
|
95
|
+
</Text>,
|
|
96
|
+
]
|
|
97
|
+
`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('renders the given errorComponent after the labelComponent when errorPosition is "belowLabel"', () => {
|
|
101
|
+
const renderAPI = render(
|
|
102
|
+
<TextInput
|
|
103
|
+
labelComponent={<Text testID="text">the labelComponent</Text>}
|
|
104
|
+
returnKeyType="done"
|
|
105
|
+
hasValidation={true}
|
|
106
|
+
errorComponent={<Text>This is the errorComponent</Text>}
|
|
107
|
+
errorPosition="belowLabel"
|
|
108
|
+
hasError={true}
|
|
109
|
+
/>,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(renderAPI.toJSON()).toMatchInlineSnapshot(`
|
|
113
|
+
Array [
|
|
114
|
+
<Text
|
|
115
|
+
accessibilityElementsHidden={true}
|
|
116
|
+
importantForAccessibility="no"
|
|
117
|
+
testID="text"
|
|
118
|
+
>
|
|
119
|
+
the labelComponent
|
|
120
|
+
</Text>,
|
|
121
|
+
<Text
|
|
122
|
+
accessibilityElementsHidden={true}
|
|
123
|
+
importantForAccessibility="no"
|
|
124
|
+
>
|
|
125
|
+
This is the errorComponent
|
|
126
|
+
</Text>,
|
|
127
|
+
<TextInput
|
|
128
|
+
accessibilityHint="This is the errorComponent"
|
|
129
|
+
accessibilityLabel="the labelComponent"
|
|
130
|
+
hasError={true}
|
|
131
|
+
hasValidation={true}
|
|
132
|
+
onLayout={[Function]}
|
|
133
|
+
onSubmitEditing={[Function]}
|
|
134
|
+
returnKeyType="done"
|
|
135
|
+
style={Object {}}
|
|
136
|
+
/>,
|
|
137
|
+
]
|
|
138
|
+
`);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('renders the given errorComponent after the input when errorPosition is "afterInput"', () => {
|
|
142
|
+
const renderAPI = render(
|
|
143
|
+
<TextInput
|
|
144
|
+
labelComponent={<Text testID="text">the labelComponent</Text>}
|
|
145
|
+
returnKeyType="done"
|
|
146
|
+
hasValidation={true}
|
|
147
|
+
errorComponent={<Text>This is the errorComponent</Text>}
|
|
148
|
+
errorPosition="afterInput"
|
|
149
|
+
hasError={true}
|
|
150
|
+
/>,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(renderAPI.toJSON()).toMatchInlineSnapshot(`
|
|
154
|
+
Array [
|
|
155
|
+
<Text
|
|
156
|
+
accessibilityElementsHidden={true}
|
|
157
|
+
importantForAccessibility="no"
|
|
158
|
+
testID="text"
|
|
159
|
+
>
|
|
160
|
+
the labelComponent
|
|
161
|
+
</Text>,
|
|
162
|
+
<TextInput
|
|
163
|
+
accessibilityHint="This is the errorComponent"
|
|
164
|
+
accessibilityLabel="the labelComponent"
|
|
165
|
+
hasError={true}
|
|
166
|
+
hasValidation={true}
|
|
167
|
+
onLayout={[Function]}
|
|
168
|
+
onSubmitEditing={[Function]}
|
|
169
|
+
returnKeyType="done"
|
|
170
|
+
style={Object {}}
|
|
171
|
+
/>,
|
|
172
|
+
<Text
|
|
173
|
+
accessibilityElementsHidden={true}
|
|
174
|
+
importantForAccessibility="no"
|
|
175
|
+
>
|
|
176
|
+
This is the errorComponent
|
|
177
|
+
</Text>,
|
|
178
|
+
]
|
|
179
|
+
`);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('hides the labelComponent from the screen readers', () => {
|
|
183
|
+
const renderAPI = render(
|
|
184
|
+
<TextInput
|
|
185
|
+
labelComponent={<Text testID="text">Test</Text>}
|
|
186
|
+
labelPosition="afterInput"
|
|
187
|
+
returnKeyType="done"
|
|
188
|
+
hasValidation={false}
|
|
189
|
+
/>,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(renderAPI.getByTestId('text').props.importantForAccessibility).toBe(
|
|
193
|
+
'no',
|
|
194
|
+
);
|
|
195
|
+
expect(
|
|
196
|
+
renderAPI.getByTestId('text').props.accessibilityElementsHidden,
|
|
197
|
+
).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('hides the errorComponent component from the screen readers', () => {
|
|
201
|
+
const renderAPI = render(
|
|
202
|
+
<TextInput
|
|
203
|
+
labelComponent={<Text>Test</Text>}
|
|
204
|
+
labelPosition="afterInput"
|
|
205
|
+
returnKeyType="done"
|
|
206
|
+
hasValidation={true}
|
|
207
|
+
errorComponent={
|
|
208
|
+
<Text testID="errorComponent-test-id">
|
|
209
|
+
This is the errorComponent
|
|
210
|
+
</Text>
|
|
211
|
+
}
|
|
212
|
+
errorPosition="afterInput"
|
|
213
|
+
hasError={true}
|
|
214
|
+
/>,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(
|
|
218
|
+
renderAPI.getByTestId('errorComponent-test-id').props
|
|
219
|
+
.importantForAccessibility,
|
|
220
|
+
).toBe('no');
|
|
221
|
+
expect(
|
|
222
|
+
renderAPI.getByTestId('errorComponent-test-id').props
|
|
223
|
+
.accessibilityElementsHidden,
|
|
224
|
+
).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('returnKeyType', () => {
|
|
228
|
+
it('sets the `returnKeyType="next"` if more fields have been registered', async () => {
|
|
229
|
+
const isLastField = jest.fn().mockReturnValue(false);
|
|
230
|
+
|
|
231
|
+
jest.spyOn(UseFormField, 'useFormField').mockReturnValue({
|
|
232
|
+
isLastField,
|
|
233
|
+
} as any);
|
|
234
|
+
|
|
235
|
+
const renderAPI = render(
|
|
236
|
+
<TextInput
|
|
237
|
+
labelComponent={<Text testID="text">labelComponent</Text>}
|
|
238
|
+
labelPosition="afterInput"
|
|
239
|
+
testID="test-id"
|
|
240
|
+
hasValidation={false}
|
|
241
|
+
/>,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
await fireEvent(renderAPI.getByTestId('test-id'), 'onLayout');
|
|
245
|
+
|
|
246
|
+
expect(renderAPI.getByTestId('test-id').props.returnKeyType).toEqual(
|
|
247
|
+
'next',
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('sets the `returnKeyType="done"` if is last field registered', () => {
|
|
252
|
+
const isLastField = jest.fn().mockReturnValue(true);
|
|
253
|
+
|
|
254
|
+
jest.spyOn(UseFormField, 'useFormField').mockReturnValue({
|
|
255
|
+
isLastField,
|
|
256
|
+
} as any);
|
|
257
|
+
|
|
258
|
+
const renderAPI = render(
|
|
259
|
+
<TextInput
|
|
260
|
+
labelComponent={<Text testID="text">labelComponent</Text>}
|
|
261
|
+
labelPosition="afterInput"
|
|
262
|
+
testID="test-id"
|
|
263
|
+
hasValidation={false}
|
|
264
|
+
/>,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
fireEvent(renderAPI.getByTestId('test-id'), 'onLayout');
|
|
268
|
+
|
|
269
|
+
expect(renderAPI.getByTestId('test-id').props.returnKeyType).toEqual(
|
|
270
|
+
'done',
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it.each([true, false])(
|
|
275
|
+
'keeps the `returnKeyType` value passed as prop',
|
|
276
|
+
async isLastFieldValue => {
|
|
277
|
+
const isLastField = jest.fn().mockReturnValue(isLastFieldValue);
|
|
278
|
+
|
|
279
|
+
jest.spyOn(UseFormField, 'useFormField').mockReturnValue({
|
|
280
|
+
isLastField,
|
|
281
|
+
} as any);
|
|
282
|
+
|
|
283
|
+
const renderAPI = render(
|
|
284
|
+
<TextInput
|
|
285
|
+
labelComponent={<Text testID="text">labelComponent</Text>}
|
|
286
|
+
labelPosition="afterInput"
|
|
287
|
+
testID="test-id"
|
|
288
|
+
returnKeyType="google"
|
|
289
|
+
hasValidation={false}
|
|
290
|
+
/>,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
await fireEvent(renderAPI.getByTestId('test-id'), 'onLayout');
|
|
294
|
+
|
|
295
|
+
expect(renderAPI.getByTestId('test-id').props.returnKeyType).toEqual(
|
|
296
|
+
'google',
|
|
297
|
+
);
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('accessibilityLabel', () => {
|
|
303
|
+
describe('Given no accessibility labelComponent is provided', () => {
|
|
304
|
+
it('Then applies the given labelComponent content as accessibilityLabel', () => {
|
|
305
|
+
const renderAPI = render(
|
|
306
|
+
<TextInput
|
|
307
|
+
labelComponent={<Text>First name:</Text>}
|
|
308
|
+
returnKeyType="done"
|
|
309
|
+
testID="text-input"
|
|
310
|
+
hasValidation={false}
|
|
311
|
+
/>,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
expect(
|
|
315
|
+
renderAPI.getByTestId('text-input').props.accessibilityLabel,
|
|
316
|
+
).toBe('First name:');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('strips the ending * from the labelComponent content before using as accessibility labelComponent', () => {
|
|
320
|
+
const renderAPI = render(
|
|
321
|
+
<TextInput
|
|
322
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
323
|
+
returnKeyType="done"
|
|
324
|
+
testID="text-input"
|
|
325
|
+
hasValidation={false}
|
|
326
|
+
/>,
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
expect(
|
|
330
|
+
renderAPI.getByTestId('text-input').props.accessibilityLabel,
|
|
331
|
+
).toBe('First name (required)');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('uses the accessibilityLabel only if provided', () => {
|
|
336
|
+
const renderAPI = render(
|
|
337
|
+
<TextInput
|
|
338
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
339
|
+
returnKeyType="done"
|
|
340
|
+
testID="text-input"
|
|
341
|
+
accessibilityLabel="Please insert your first name"
|
|
342
|
+
hasValidation={false}
|
|
343
|
+
/>,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
expect(renderAPI.getByTestId('text-input').props.accessibilityLabel).toBe(
|
|
347
|
+
'Please insert your first name',
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('calls focusNextFormField onSubmitEditing event', () => {
|
|
353
|
+
const focusNextFormField = jest.fn();
|
|
354
|
+
|
|
355
|
+
jest.spyOn(UseFormField, 'useFormField').mockReturnValue({
|
|
356
|
+
focusNextFormField,
|
|
357
|
+
} as any);
|
|
358
|
+
|
|
359
|
+
const fn = jest.fn();
|
|
360
|
+
|
|
361
|
+
const renderAPI = render(
|
|
362
|
+
<TextInput
|
|
363
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
364
|
+
returnKeyType="next"
|
|
365
|
+
testID="text-input"
|
|
366
|
+
accessibilityLabel="Please insert your first name"
|
|
367
|
+
onSubmitEditing={fn}
|
|
368
|
+
hasValidation={false}
|
|
369
|
+
/>,
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
fireEvent(
|
|
373
|
+
renderAPI.getByTestId('text-input'),
|
|
374
|
+
'onSubmitEditing',
|
|
375
|
+
'whatever',
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
expect(fn).toHaveBeenCalledWith('whatever');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('ignores the errorComponent message if hasError is not true', () => {
|
|
382
|
+
const renderAPI = render(
|
|
383
|
+
<TextInput
|
|
384
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
385
|
+
returnKeyType="next"
|
|
386
|
+
testID="text-input"
|
|
387
|
+
accessibilityLabel="Please insert your first name"
|
|
388
|
+
hasValidation={true}
|
|
389
|
+
hasError={false}
|
|
390
|
+
errorMessage={'This is the errorComponent'}
|
|
391
|
+
errorComponent={<></>}
|
|
392
|
+
/>,
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
expect(renderAPI.getByTestId('text-input').props.accessibilityHint).toBe(
|
|
396
|
+
'',
|
|
397
|
+
);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('when hasError uses the text from the errorComponent as part of the accessibilityHint', () => {
|
|
401
|
+
const renderAPI = render(
|
|
402
|
+
<TextInput
|
|
403
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
404
|
+
returnKeyType="next"
|
|
405
|
+
testID="text-input"
|
|
406
|
+
accessibilityLabel="Please insert your first name"
|
|
407
|
+
accessibilityHint="The hint"
|
|
408
|
+
hasValidation={true}
|
|
409
|
+
hasError={true}
|
|
410
|
+
errorComponent={<Text>The first name cannot be blank</Text>}
|
|
411
|
+
/>,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
expect(renderAPI.getByTestId('text-input').props.accessibilityHint).toBe(
|
|
415
|
+
'The hint, The first name cannot be blank',
|
|
416
|
+
);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('when hasError uses the errorText, if specified, as part of the accessibilityHint', () => {
|
|
420
|
+
const renderAPI = render(
|
|
421
|
+
<TextInput
|
|
422
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
423
|
+
returnKeyType="next"
|
|
424
|
+
testID="text-input"
|
|
425
|
+
accessibilityLabel="Please insert your first name"
|
|
426
|
+
accessibilityHint="The hint"
|
|
427
|
+
hasValidation={true}
|
|
428
|
+
hasError={true}
|
|
429
|
+
errorMessage="This text will be used"
|
|
430
|
+
errorComponent={<Text>The first name cannot be blank</Text>}
|
|
431
|
+
/>,
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
expect(renderAPI.getByTestId('text-input').props.accessibilityHint).toBe(
|
|
435
|
+
'The hint, This text will be used',
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('apply the style returned by useFormField', () => {
|
|
440
|
+
const focusNextFormField = jest.fn();
|
|
441
|
+
|
|
442
|
+
jest.spyOn(UseFormField, 'useFormField').mockReturnValue({
|
|
443
|
+
focusNextFormField,
|
|
444
|
+
style: ERROR_STYLE,
|
|
445
|
+
} as any);
|
|
446
|
+
|
|
447
|
+
const renderAPI = render(
|
|
448
|
+
<TextInput
|
|
449
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
450
|
+
returnKeyType="next"
|
|
451
|
+
testID="text-input"
|
|
452
|
+
accessibilityLabel="Please insert your first name"
|
|
453
|
+
hasValidation={false}
|
|
454
|
+
/>,
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
expect(renderAPI.getByTestId('text-input').props.style).toEqual(
|
|
458
|
+
ERROR_STYLE,
|
|
459
|
+
);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe('When __DEV__ is false', () => {
|
|
463
|
+
let TextInputWithoutDev: typeof TextInput;
|
|
464
|
+
let accessibilityLabelChecker: jest.Mock;
|
|
465
|
+
let uppercaseChecker: jest.Mock;
|
|
466
|
+
let onLayout: jest.Mock;
|
|
467
|
+
let noUndefinedProperty: jest.Mock;
|
|
468
|
+
|
|
469
|
+
beforeEach(function () {
|
|
470
|
+
// @ts-ignore
|
|
471
|
+
global.__DEV__ = false;
|
|
472
|
+
|
|
473
|
+
accessibilityLabelChecker = jest.fn();
|
|
474
|
+
noUndefinedProperty = jest.fn();
|
|
475
|
+
uppercaseChecker = jest.fn();
|
|
476
|
+
onLayout = jest.fn();
|
|
477
|
+
|
|
478
|
+
// @ts-ignore
|
|
479
|
+
jest.spyOn(UseChecks, 'useChecks').mockReturnValue({
|
|
480
|
+
noUppercaseStringChecker: accessibilityLabelChecker,
|
|
481
|
+
noUndefinedProperty,
|
|
482
|
+
uppercaseChecker,
|
|
483
|
+
onLayout,
|
|
484
|
+
} as any);
|
|
485
|
+
|
|
486
|
+
TextInputWithoutDev = require('./TextInput').TextInput;
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('does not perform any check', () => {
|
|
490
|
+
render(
|
|
491
|
+
<TextInputWithoutDev
|
|
492
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
493
|
+
returnKeyType="next"
|
|
494
|
+
testID="text-input"
|
|
495
|
+
accessibilityLabel="Please insert your first name"
|
|
496
|
+
hasValidation={false}
|
|
497
|
+
/>,
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
expect(noUndefinedProperty).not.toHaveBeenCalled();
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('does not apply the debug style', () => {
|
|
504
|
+
jest.spyOn(UseFormField, 'useFormField').mockReturnValue({
|
|
505
|
+
style: ERROR_STYLE,
|
|
506
|
+
} as any);
|
|
507
|
+
|
|
508
|
+
const { getByTestId } = render(
|
|
509
|
+
<TextInputWithoutDev
|
|
510
|
+
labelComponent={<Text>First name (required)*</Text>}
|
|
511
|
+
returnKeyType="next"
|
|
512
|
+
testID="text-input"
|
|
513
|
+
accessibilityLabel="Please insert your first name"
|
|
514
|
+
hasValidation={false}
|
|
515
|
+
/>,
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
expect(getByTestId('text-input').props.style).toEqual(undefined);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
jest.mock('../hooks/useFormField', () => {
|
|
524
|
+
return {
|
|
525
|
+
useFormField: jest.fn().mockReturnValue({}),
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
jest.mock('@react-native-ama/core', () => {
|
|
530
|
+
const originalModule = jest.requireActual('@react-native-ama/core');
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
...originalModule,
|
|
534
|
+
useAMAContext: () => {
|
|
535
|
+
return {
|
|
536
|
+
isScreenReaderEnabled: true,
|
|
537
|
+
};
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { HideChildrenFromAccessibilityTree } from '@react-native-ama/core';
|
|
2
|
+
import { useChecks } from '@react-native-ama/core';
|
|
3
|
+
import { applyStyle } from '@react-native-ama/internal';
|
|
4
|
+
import { maybeGenerateStringFromElement } from '@react-native-ama/internal';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import {
|
|
7
|
+
TextInput as RNTextInput,
|
|
8
|
+
TextInputProps as RNTextInputProps,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
|
|
11
|
+
import { useTextInput } from '../hooks/useTextInput';
|
|
12
|
+
|
|
13
|
+
export type TextInputProps = RNTextInputProps & {
|
|
14
|
+
labelComponent: JSX.Element;
|
|
15
|
+
labelPosition?: 'beforeInput' | 'afterInput';
|
|
16
|
+
nextFormField?: React.RefObject<RNTextInput>;
|
|
17
|
+
id?: string;
|
|
18
|
+
nextFieldId?: string;
|
|
19
|
+
hasValidation: boolean;
|
|
20
|
+
errorComponent?: JSX.Element;
|
|
21
|
+
hasError?: boolean;
|
|
22
|
+
errorPosition?: 'belowLabel' | 'afterInput';
|
|
23
|
+
errorMessage?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const TextInput = React.forwardRef<RNTextInput, TextInputProps>(
|
|
27
|
+
(
|
|
28
|
+
{
|
|
29
|
+
labelComponent,
|
|
30
|
+
labelPosition = 'beforeInput',
|
|
31
|
+
nextFormField,
|
|
32
|
+
id,
|
|
33
|
+
nextFieldId,
|
|
34
|
+
accessibilityHint,
|
|
35
|
+
hasError,
|
|
36
|
+
errorComponent,
|
|
37
|
+
errorPosition = 'afterInput',
|
|
38
|
+
hasValidation,
|
|
39
|
+
errorMessage,
|
|
40
|
+
...rest
|
|
41
|
+
},
|
|
42
|
+
forwardedRef,
|
|
43
|
+
) => {
|
|
44
|
+
const inputRef = React.useRef<React.ElementRef<typeof TextInput> | null>(
|
|
45
|
+
null,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
React.useImperativeHandle(forwardedRef, () => inputRef.current!);
|
|
49
|
+
|
|
50
|
+
const showLabelBeforeInput = labelPosition === 'beforeInput';
|
|
51
|
+
|
|
52
|
+
const accessibilityLabel = React.useMemo(
|
|
53
|
+
() =>
|
|
54
|
+
maybeGenerateStringFromElement(labelComponent, rest.accessibilityLabel),
|
|
55
|
+
[labelComponent, rest.accessibilityLabel],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
const textInputProps = useTextInput({
|
|
60
|
+
required: false,
|
|
61
|
+
ref: inputRef,
|
|
62
|
+
id,
|
|
63
|
+
nextFieldId,
|
|
64
|
+
nextFormFieldRef: nextFormField,
|
|
65
|
+
hasError,
|
|
66
|
+
hasValidation,
|
|
67
|
+
errorMessage,
|
|
68
|
+
accessibilityLabel,
|
|
69
|
+
...rest,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const checks = __DEV__ ? useChecks?.() : undefined;
|
|
73
|
+
__DEV__ &&
|
|
74
|
+
checks?.noUndefinedProperty({
|
|
75
|
+
properties: { labelComponent },
|
|
76
|
+
property: 'labelComponent',
|
|
77
|
+
rule: 'NO_FORM_LABEL',
|
|
78
|
+
});
|
|
79
|
+
__DEV__ &&
|
|
80
|
+
checks?.noUndefinedProperty({
|
|
81
|
+
properties: { accessibilityLabel },
|
|
82
|
+
property: 'accessibilityLabel',
|
|
83
|
+
rule: 'NO_ACCESSIBILITY_LABEL',
|
|
84
|
+
});
|
|
85
|
+
__DEV__ &&
|
|
86
|
+
accessibilityLabel?.trim() === '' &&
|
|
87
|
+
checks?.logResult('NO_ACCESSIBILITY_LABEL', {
|
|
88
|
+
rule: 'NO_ACCESSIBILITY_LABEL',
|
|
89
|
+
message: 'No accessibility label provided',
|
|
90
|
+
});
|
|
91
|
+
__DEV__ &&
|
|
92
|
+
hasValidation &&
|
|
93
|
+
checks?.noUndefinedProperty({
|
|
94
|
+
// @ts-ignore
|
|
95
|
+
properties: { errorComponent },
|
|
96
|
+
property: 'errorComponent',
|
|
97
|
+
rule: 'NO_FORM_ERROR',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const style =
|
|
101
|
+
__DEV__ && // @ts-ignore
|
|
102
|
+
applyStyle?.({
|
|
103
|
+
// @ts-ignore
|
|
104
|
+
style: textInputProps?.style || {},
|
|
105
|
+
debugStyle: checks?.debugStyle,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const accessibilityHiddenLabel = React.useMemo(
|
|
109
|
+
() => (
|
|
110
|
+
<HideChildrenFromAccessibilityTree>
|
|
111
|
+
{labelComponent}
|
|
112
|
+
</HideChildrenFromAccessibilityTree>
|
|
113
|
+
),
|
|
114
|
+
[labelComponent],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const errorText = React.useMemo(() => {
|
|
118
|
+
return hasValidation && hasError
|
|
119
|
+
? maybeGenerateStringFromElement(errorComponent, errorMessage)
|
|
120
|
+
: '';
|
|
121
|
+
}, [hasValidation, hasError, errorComponent, errorMessage]);
|
|
122
|
+
|
|
123
|
+
const renderError = () => {
|
|
124
|
+
return (
|
|
125
|
+
<HideChildrenFromAccessibilityTree>
|
|
126
|
+
{errorComponent}
|
|
127
|
+
</HideChildrenFromAccessibilityTree>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const fullAccessibilityHint = [accessibilityHint || '', errorText || '']
|
|
132
|
+
.filter(item => item.length > 0)
|
|
133
|
+
?.join(', ');
|
|
134
|
+
|
|
135
|
+
return __DEV__ ? (
|
|
136
|
+
<>
|
|
137
|
+
{showLabelBeforeInput ? accessibilityHiddenLabel : null}
|
|
138
|
+
{hasError && errorPosition === 'belowLabel' ? renderError() : null}
|
|
139
|
+
<RNTextInput
|
|
140
|
+
// @ts-ignore
|
|
141
|
+
ref={inputRef}
|
|
142
|
+
{...rest}
|
|
143
|
+
{...textInputProps}
|
|
144
|
+
style={style}
|
|
145
|
+
accessibilityLabel={accessibilityLabel!}
|
|
146
|
+
accessibilityHint={fullAccessibilityHint}
|
|
147
|
+
/>
|
|
148
|
+
{showLabelBeforeInput ? null : accessibilityHiddenLabel}
|
|
149
|
+
{hasError && errorPosition === 'afterInput' ? renderError() : null}
|
|
150
|
+
</>
|
|
151
|
+
) : (
|
|
152
|
+
<>
|
|
153
|
+
{showLabelBeforeInput ? accessibilityHiddenLabel : null}
|
|
154
|
+
{hasError && errorPosition === 'belowLabel' ? renderError() : null}
|
|
155
|
+
<RNTextInput
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
ref={inputRef}
|
|
158
|
+
{...rest}
|
|
159
|
+
{...textInputProps}
|
|
160
|
+
accessibilityLabel={accessibilityLabel!}
|
|
161
|
+
accessibilityHint={fullAccessibilityHint}
|
|
162
|
+
/>
|
|
163
|
+
{showLabelBeforeInput ? null : accessibilityHiddenLabel}
|
|
164
|
+
{hasError && errorPosition === 'afterInput' ? renderError() : null}
|
|
165
|
+
</>
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
);
|