@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.
Files changed (110) hide show
  1. package/dist/core/src/components/AMAProvider.d.ts +26 -0
  2. package/dist/core/src/components/AMAProvider.js +147 -0
  3. package/dist/core/src/components/AutofocusContainer.d.ts +9 -0
  4. package/dist/core/src/components/AutofocusContainer.js +58 -0
  5. package/dist/core/src/components/HideChildrenFromAccessibilityTree.d.ts +6 -0
  6. package/dist/core/src/components/HideChildrenFromAccessibilityTree.js +40 -0
  7. package/dist/core/src/hooks/useButtonChecks.d.ts +6 -0
  8. package/dist/core/src/hooks/useButtonChecks.js +51 -0
  9. package/dist/core/src/hooks/useChecks.d.ts +15 -0
  10. package/dist/core/src/hooks/useChecks.js +152 -0
  11. package/dist/core/src/hooks/useFocus.d.ts +4 -0
  12. package/dist/core/src/hooks/useFocus.js +55 -0
  13. package/dist/core/src/hooks/useTimedAction.d.ts +3 -0
  14. package/dist/core/src/hooks/useTimedAction.js +63 -0
  15. package/dist/core/src/index.d.ts +7 -0
  16. package/dist/core/src/index.js +20 -0
  17. package/dist/forms/src/components/Form.d.ts +24 -0
  18. package/dist/forms/src/components/Form.js +122 -0
  19. package/dist/forms/src/components/FormField.d.ts +7 -0
  20. package/dist/forms/src/components/FormField.js +52 -0
  21. package/dist/forms/src/components/FormSubmit.d.ts +10 -0
  22. package/dist/forms/src/components/FormSubmit.js +25 -0
  23. package/dist/forms/src/components/FormSwitch.d.ts +4 -0
  24. package/dist/forms/src/components/FormSwitch.js +15 -0
  25. package/dist/forms/src/components/TextInput.d.ts +26 -0
  26. package/dist/forms/src/components/TextInput.js +123 -0
  27. package/dist/forms/src/hooks/useFormField.d.ts +26 -0
  28. package/dist/forms/src/hooks/useFormField.js +92 -0
  29. package/dist/forms/src/hooks/useTextInput.d.ts +350 -0
  30. package/dist/forms/src/hooks/useTextInput.js +102 -0
  31. package/dist/forms/src/index.d.ts +16 -0
  32. package/dist/forms/src/index.js +24 -0
  33. package/dist/internal/src/checks/checkAccessibilityRole.d.ts +3 -0
  34. package/dist/internal/src/checks/checkAccessibilityRole.js +32 -0
  35. package/dist/internal/src/checks/checkFocusTrap.d.ts +8 -0
  36. package/dist/internal/src/checks/checkFocusTrap.js +34 -0
  37. package/dist/internal/src/checks/checkForAccessibilityState.d.ts +7 -0
  38. package/dist/internal/src/checks/checkForAccessibilityState.js +48 -0
  39. package/dist/internal/src/checks/checkMinimumSize.d.ts +3 -0
  40. package/dist/internal/src/checks/checkMinimumSize.js +29 -0
  41. package/dist/internal/src/checks/contrastChecker.d.ts +8 -0
  42. package/dist/internal/src/checks/contrastChecker.js +77 -0
  43. package/dist/internal/src/checks/noUndefinedProperty.d.ts +8 -0
  44. package/dist/internal/src/checks/noUndefinedProperty.js +14 -0
  45. package/dist/internal/src/checks/uppercaseChecker.d.ts +8 -0
  46. package/dist/internal/src/checks/uppercaseChecker.js +18 -0
  47. package/dist/internal/src/checks/uppercaseStringChecker.d.ts +8 -0
  48. package/dist/internal/src/checks/uppercaseStringChecker.js +29 -0
  49. package/dist/internal/src/index.d.ts +20 -0
  50. package/dist/internal/src/index.js +56 -0
  51. package/dist/internal/src/types.d.ts +17 -0
  52. package/dist/internal/src/types.js +2 -0
  53. package/dist/internal/src/utils/applyStyle.d.ts +9 -0
  54. package/dist/internal/src/utils/applyStyle.js +33 -0
  55. package/dist/internal/src/utils/constants.d.ts +3 -0
  56. package/dist/internal/src/utils/constants.js +32 -0
  57. package/dist/internal/src/utils/error.style.d.ts +6 -0
  58. package/dist/internal/src/utils/error.style.js +11 -0
  59. package/dist/internal/src/utils/generateAccessibilityStateFromProp.d.ts +9 -0
  60. package/dist/internal/src/utils/generateAccessibilityStateFromProp.js +35 -0
  61. package/dist/internal/src/utils/getPropertyFromStyle.d.ts +2 -0
  62. package/dist/internal/src/utils/getPropertyFromStyle.js +10 -0
  63. package/dist/internal/src/utils/interpolateAnimationStates.d.ts +2 -0
  64. package/dist/internal/src/utils/interpolateAnimationStates.js +43 -0
  65. package/dist/internal/src/utils/isFocused.d.ts +2 -0
  66. package/dist/internal/src/utils/isFocused.js +7 -0
  67. package/dist/internal/src/utils/logger.d.ts +16 -0
  68. package/dist/internal/src/utils/logger.js +53 -0
  69. package/dist/internal/src/utils/logger.rules.d.ts +15 -0
  70. package/dist/internal/src/utils/logger.rules.js +74 -0
  71. package/dist/internal/src/utils/maybeGenerateStringFromElement.d.ts +2 -0
  72. package/dist/internal/src/utils/maybeGenerateStringFromElement.js +51 -0
  73. package/dist/internal/src/utils/minimumTouchableSize.d.ts +3 -0
  74. package/dist/internal/src/utils/minimumTouchableSize.js +9 -0
  75. package/dist/react-native/src/components/ExpandablePressable.d.ts +6 -0
  76. package/dist/react-native/src/components/ExpandablePressable.js +26 -0
  77. package/dist/react-native/src/components/Pressable.d.ts +5 -0
  78. package/dist/react-native/src/components/Pressable.js +49 -0
  79. package/dist/react-native/src/components/SwitchListItem.d.ts +12 -0
  80. package/dist/react-native/src/components/SwitchListItem.js +44 -0
  81. package/dist/react-native/src/components/SwitchWrapper.d.ts +11 -0
  82. package/dist/react-native/src/components/SwitchWrapper.js +36 -0
  83. package/dist/react-native/src/components/Text.d.ts +6 -0
  84. package/dist/react-native/src/components/Text.js +77 -0
  85. package/dist/react-native/src/components/TouchableOpacity.d.ts +6 -0
  86. package/dist/react-native/src/components/TouchableOpacity.js +48 -0
  87. package/dist/react-native/src/components/TouchableWithoutFeedback.d.ts +5 -0
  88. package/dist/react-native/src/components/TouchableWithoutFeedback.js +48 -0
  89. package/dist/react-native/src/hooks/useExpandable.d.ts +13 -0
  90. package/dist/react-native/src/hooks/useExpandable.js +31 -0
  91. package/dist/react-native/src/hooks/usePressable.d.ts +16 -0
  92. package/dist/react-native/src/hooks/usePressable.js +44 -0
  93. package/dist/react-native/src/hooks/useSwitch.d.ts +10 -0
  94. package/dist/react-native/src/hooks/useSwitch.js +25 -0
  95. package/dist/react-native/src/index.d.ts +10 -0
  96. package/dist/react-native/src/index.js +25 -0
  97. package/package.json +82 -0
  98. package/src/components/Form.test.tsx +262 -0
  99. package/src/components/Form.tsx +138 -0
  100. package/src/components/FormField.test.tsx +54 -0
  101. package/src/components/FormField.tsx +45 -0
  102. package/src/components/FormSubmit.test.tsx +71 -0
  103. package/src/components/FormSubmit.tsx +43 -0
  104. package/src/components/FormSwitch.tsx +17 -0
  105. package/src/components/TextInput.test.tsx +540 -0
  106. package/src/components/TextInput.tsx +168 -0
  107. package/src/hooks/useFormField.test.tsx +183 -0
  108. package/src/hooks/useFormField.ts +133 -0
  109. package/src/hooks/useTextInput.ts +128 -0
  110. 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
+ );