@react-native-ama/forms 1.2.0 → 2.0.0-beta.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/components/Form.d.ts +5 -2
- package/dist/components/Form.js +14 -31
- package/dist/components/FormSubmit.d.ts +7 -9
- package/dist/components/FormSubmit.js +2 -14
- package/dist/components/TextInput.d.ts +36 -4
- package/dist/components/TextInput.js +20 -73
- package/dist/hooks/useFocus.d.ts +4 -0
- package/dist/hooks/useFocus.js +69 -0
- package/dist/hooks/useFormField.d.ts +1 -7
- package/dist/hooks/useFormField.js +32 -25
- package/dist/hooks/useFormSubmit.d.ts +4 -0
- package/dist/hooks/useFormSubmit.js +14 -0
- package/dist/hooks/useTextInput.d.ts +59 -231
- package/dist/hooks/useTextInput.js +2 -37
- package/dist/index.d.ts +10 -13
- package/dist/index.js +3 -8
- package/dist/utils/checkFocusTrap.d.ts +8 -0
- package/dist/utils/checkFocusTrap.js +30 -0
- package/package.json +59 -18
- package/src/components/Form.test.tsx +63 -70
- package/src/components/Form.tsx +28 -52
- package/src/components/FormField.test.tsx +32 -16
- package/src/components/FormField.tsx +0 -1
- package/src/components/FormSubmit.test.tsx +17 -55
- package/src/components/FormSubmit.tsx +9 -36
- package/src/components/TextInput.test.tsx +83 -397
- package/src/components/TextInput.tsx +41 -109
- package/src/hooks/useFocus.test.ts +130 -0
- package/src/hooks/useFocus.ts +62 -0
- package/src/hooks/useFormField.test.tsx +183 -78
- package/src/hooks/useFormField.ts +36 -28
- package/src/hooks/useFormSubmit.test.tsx +18 -0
- package/src/hooks/useFormSubmit.ts +17 -0
- package/src/hooks/useTextInput.ts +10 -55
- package/src/index.ts +15 -20
- package/src/utils/checkFocusTrap.test.ts +80 -0
- package/src/utils/checkFocusTrap.ts +40 -0
- package/dist/components/FormSwitch.d.ts +0 -4
- package/dist/components/FormSwitch.js +0 -15
- package/src/components/FormSwitch.tsx +0 -17
package/package.json
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-ama/forms",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"types": "dist/index.d.ts",
|
|
6
|
-
"main": "dist/index.js",
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
|
+
"sideEffects": false,
|
|
7
5
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"react-native": "./src/index.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./Form": {
|
|
12
|
+
"types": "./dist/components/Form.d.ts",
|
|
13
|
+
"default": "./dist/components/Form.js"
|
|
14
|
+
},
|
|
15
|
+
"./FormField": {
|
|
16
|
+
"types": "./dist/components/FormField.d.ts",
|
|
17
|
+
"default": "./dist/components/FormField.js"
|
|
18
|
+
},
|
|
19
|
+
"./FormSubmit": {
|
|
20
|
+
"types": "./dist/components/FormSubmit.d.ts",
|
|
21
|
+
"default": "./dist/components/FormSubmit.js"
|
|
22
|
+
},
|
|
23
|
+
"./TextInput": {
|
|
24
|
+
"types": "./dist/components/TextInput.d.ts",
|
|
25
|
+
"default": "./dist/components/TextInput.js"
|
|
26
|
+
},
|
|
27
|
+
"./useFormField": {
|
|
28
|
+
"types": "./dist/hooks/useFormField.d.ts",
|
|
29
|
+
"default": "./dist/hooks/useFormField.js"
|
|
30
|
+
},
|
|
31
|
+
"./useFormSubmit": {
|
|
32
|
+
"types": "./dist/hooks/useFormSubmit.d.ts",
|
|
33
|
+
"default": "./dist/hooks/useFormSubmit.js"
|
|
34
|
+
},
|
|
35
|
+
"./useTextInput": {
|
|
36
|
+
"types": "./dist/hooks/useTextInput.d.ts",
|
|
37
|
+
"default": "./dist/hooks/useTextInput.js"
|
|
38
|
+
}
|
|
15
39
|
},
|
|
16
40
|
"files": [
|
|
17
41
|
"src",
|
|
@@ -22,15 +46,16 @@
|
|
|
22
46
|
"typecheck": "tsc --noEmit",
|
|
23
47
|
"test": "jest"
|
|
24
48
|
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@react-native-ama/core": "~1.2.0",
|
|
27
|
-
"@react-native-ama/internal": "~1.2.0",
|
|
28
|
-
"@react-native-ama/react-native": "~1.2.0"
|
|
29
|
-
},
|
|
30
49
|
"peerDependencies": {
|
|
50
|
+
"@react-native-ama/core": "~2.0.0-beta.0",
|
|
31
51
|
"react": "*",
|
|
32
52
|
"react-native": "*"
|
|
33
53
|
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"@react-native-ama/core": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
34
59
|
"keywords": [
|
|
35
60
|
"react-native",
|
|
36
61
|
"a11y",
|
|
@@ -51,7 +76,10 @@
|
|
|
51
76
|
"author": "",
|
|
52
77
|
"license": "MIT",
|
|
53
78
|
"jest": {
|
|
54
|
-
"preset": "
|
|
79
|
+
"preset": "react-native",
|
|
80
|
+
"setupFilesAfterEnv": [
|
|
81
|
+
"<rootDir>/jest.setup.js"
|
|
82
|
+
],
|
|
55
83
|
"modulePathIgnorePatterns": [
|
|
56
84
|
"<rootDir>/node_modules",
|
|
57
85
|
"<rootDir>/dist/"
|
|
@@ -68,11 +96,24 @@
|
|
|
68
96
|
}
|
|
69
97
|
},
|
|
70
98
|
"transformIgnorePatterns": [
|
|
71
|
-
"node_modules/(?!(
|
|
99
|
+
"node_modules/(?!(react-native|react-native-reanimated|expo|@expo)/)"
|
|
72
100
|
],
|
|
73
101
|
"transform": {
|
|
74
102
|
"\\.[jt]sx?$": "babel-jest"
|
|
75
103
|
},
|
|
104
|
+
"moduleDirectories": [
|
|
105
|
+
"node_modules",
|
|
106
|
+
"../../playground/node_modules"
|
|
107
|
+
],
|
|
108
|
+
"moduleNameMapper": {
|
|
109
|
+
"@react-native/js-polyfills/error-guard": "<rootDir>/../../jest-mocks/error-guard.js",
|
|
110
|
+
"^(\\.{1,2}/)*ama\\.config\\.json$": "<rootDir>/../../jest-mocks/ama-config.js",
|
|
111
|
+
"^(\\.{1,2}/)*ReactNativeAmaModule$": "<rootDir>/../../jest-mocks/ReactNativeAmaModule.js",
|
|
112
|
+
"^@react-native-ama/core$": "<rootDir>/../../packages/core/src/index.ts",
|
|
113
|
+
"^@react-native-ama/core/(.*)$": "<rootDir>/../../packages/core/$1",
|
|
114
|
+
"^react-native$": "<rootDir>/../../node_modules/react-native",
|
|
115
|
+
"^react-native/(.*)$": "<rootDir>/../../node_modules/react-native/$1"
|
|
116
|
+
},
|
|
76
117
|
"verbose": true
|
|
77
118
|
},
|
|
78
119
|
"devDependencies": {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import * as UseChecks from '@react-native-ama/core/src/hooks/useChecks';
|
|
2
1
|
import { render, waitFor } from '@testing-library/react-native';
|
|
3
2
|
import * as React from 'react';
|
|
4
|
-
|
|
5
3
|
import { Form, FormContext } from './Form';
|
|
6
4
|
|
|
7
5
|
beforeEach(() => {
|
|
@@ -9,56 +7,6 @@ beforeEach(() => {
|
|
|
9
7
|
});
|
|
10
8
|
|
|
11
9
|
describe('Form', () => {
|
|
12
|
-
let checkFocusTrap: jest.Mock;
|
|
13
|
-
|
|
14
|
-
beforeEach(function () {
|
|
15
|
-
checkFocusTrap = jest.fn().mockResolvedValue(null);
|
|
16
|
-
|
|
17
|
-
// @ts-ignore
|
|
18
|
-
jest.spyOn(UseChecks, 'useChecks').mockReturnValue({
|
|
19
|
-
checkFocusTrap,
|
|
20
|
-
hasErrors: true,
|
|
21
|
-
} as any);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('checks that next field has the focus if it has the focus callback', async () => {
|
|
25
|
-
const focus = jest.fn();
|
|
26
|
-
const ref = { current: { focus, isFocused: jest.fn() } };
|
|
27
|
-
const secondField = {
|
|
28
|
-
ref,
|
|
29
|
-
hasFocusCallback: true,
|
|
30
|
-
hasValidation: false,
|
|
31
|
-
isEditable: true,
|
|
32
|
-
id: 'second',
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
let providerValues = customRender();
|
|
36
|
-
|
|
37
|
-
providerValues.refs?.push({
|
|
38
|
-
ref: { current: 'random ref' },
|
|
39
|
-
hasFocusCallback: false,
|
|
40
|
-
hasValidation: false,
|
|
41
|
-
id: 'first',
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// next field
|
|
45
|
-
providerValues.refs?.push(secondField);
|
|
46
|
-
|
|
47
|
-
providerValues.focusField?.(secondField);
|
|
48
|
-
|
|
49
|
-
await waitFor(() =>
|
|
50
|
-
expect(checkFocusTrap).toHaveBeenCalledWith({
|
|
51
|
-
ref: {
|
|
52
|
-
current: {
|
|
53
|
-
focus: expect.any(Function),
|
|
54
|
-
isFocused: expect.any(Function),
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
shouldHaveFocus: true,
|
|
58
|
-
}),
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
10
|
it('calls setFocus if the next field has no .focus callback', () => {
|
|
63
11
|
const ref = { current: 'whatever' };
|
|
64
12
|
const secondField = {
|
|
@@ -141,24 +89,11 @@ describe('Form', () => {
|
|
|
141
89
|
providerValues.focusField?.(secondField);
|
|
142
90
|
|
|
143
91
|
expect(focus).not.toHaveBeenCalled();
|
|
144
|
-
expect(checkFocusTrap).not.toHaveBeenCalled();
|
|
145
92
|
|
|
146
93
|
jest.advanceTimersByTime(51);
|
|
147
94
|
|
|
148
95
|
expect(focus).toHaveBeenCalledWith();
|
|
149
96
|
|
|
150
|
-
await waitFor(() =>
|
|
151
|
-
expect(checkFocusTrap).toHaveBeenCalledWith({
|
|
152
|
-
ref: {
|
|
153
|
-
current: {
|
|
154
|
-
focus: expect.any(Function),
|
|
155
|
-
isFocused: expect.any(Function),
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
shouldHaveFocus: true,
|
|
159
|
-
}),
|
|
160
|
-
);
|
|
161
|
-
|
|
162
97
|
jest.useRealTimers();
|
|
163
98
|
});
|
|
164
99
|
|
|
@@ -190,6 +125,35 @@ describe('Form', () => {
|
|
|
190
125
|
expect(setFocus).toHaveBeenCalledWith(ref.current);
|
|
191
126
|
});
|
|
192
127
|
|
|
128
|
+
it('focusFieldAt focuses the field at the given index', async () => {
|
|
129
|
+
const focus = jest.fn();
|
|
130
|
+
const ref = React.createRef<any>();
|
|
131
|
+
(ref as any).current = { focus, isFocused: jest.fn().mockReturnValue(false) };
|
|
132
|
+
|
|
133
|
+
let formRef = React.createRef<any>();
|
|
134
|
+
|
|
135
|
+
render(
|
|
136
|
+
<Form ref={formRef} onSubmit={jest.fn()}>
|
|
137
|
+
<FormContext.Consumer>
|
|
138
|
+
{value => {
|
|
139
|
+
value?.refs?.push({
|
|
140
|
+
ref: { current: ref } as any,
|
|
141
|
+
hasFocusCallback: true,
|
|
142
|
+
hasValidation: false,
|
|
143
|
+
isEditable: true,
|
|
144
|
+
id: 'field0',
|
|
145
|
+
});
|
|
146
|
+
return null;
|
|
147
|
+
}}
|
|
148
|
+
</FormContext.Consumer>
|
|
149
|
+
</Form>,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
formRef.current?.focusFieldAt(0);
|
|
153
|
+
|
|
154
|
+
await waitFor(() => expect(focus).toHaveBeenCalled());
|
|
155
|
+
});
|
|
156
|
+
|
|
193
157
|
it('focuses the first failed component when submitForm fails', async () => {
|
|
194
158
|
const onSubmit = jest.fn().mockReturnValue(false);
|
|
195
159
|
|
|
@@ -222,7 +186,7 @@ describe('Form', () => {
|
|
|
222
186
|
});
|
|
223
187
|
});
|
|
224
188
|
|
|
225
|
-
|
|
189
|
+
var setFocus: jest.Mock;
|
|
226
190
|
|
|
227
191
|
function mockUseFocus() {
|
|
228
192
|
setFocus = jest.fn();
|
|
@@ -255,8 +219,37 @@ function customRender(onSubmit = jest.fn()) {
|
|
|
255
219
|
}
|
|
256
220
|
|
|
257
221
|
jest.mock('../hooks/useFocus', () => mockUseFocus());
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
222
|
+
|
|
223
|
+
describe('useForm', () => {
|
|
224
|
+
it('returns default context value when suppressError is true and no Form wrapper', () => {
|
|
225
|
+
const { useForm } = require('./Form');
|
|
226
|
+
const { renderHook } = require('@testing-library/react-native');
|
|
227
|
+
|
|
228
|
+
const { result } = renderHook(() => useForm({ suppressError: true }));
|
|
229
|
+
expect(result.current.refs).toEqual([]);
|
|
230
|
+
expect(typeof result.current.submitForm).toBe('function');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('throws when called outside Form and suppressError is false', () => {
|
|
234
|
+
const { useForm } = require('./Form');
|
|
235
|
+
const { renderHook } = require('@testing-library/react-native');
|
|
236
|
+
|
|
237
|
+
expect(() => renderHook(() => useForm())).toThrow(
|
|
238
|
+
'useForm must be used within a FormContextProvider',
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('logs a console.error in __DEV__ when called outside Form', () => {
|
|
243
|
+
const { useForm } = require('./Form');
|
|
244
|
+
const { renderHook } = require('@testing-library/react-native');
|
|
245
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
246
|
+
// @ts-ignore
|
|
247
|
+
global.__DEV__ = true;
|
|
248
|
+
|
|
249
|
+
expect(() => renderHook(() => useForm())).toThrow();
|
|
250
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
251
|
+
errorSpy.mockRestore();
|
|
252
|
+
// @ts-ignore
|
|
253
|
+
global.__DEV__ = true;
|
|
254
|
+
});
|
|
262
255
|
});
|
package/src/components/Form.tsx
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { useChecks, useFocus } from '@react-native-ama/core';
|
|
2
|
-
import { isFocused } from '@react-native-ama/internal';
|
|
3
1
|
import * as React from 'react';
|
|
4
|
-
import { InteractionManager } from 'react-native';
|
|
2
|
+
import { InteractionManager, TextInput } from 'react-native';
|
|
3
|
+
import { useFocus } from '../hooks/useFocus';
|
|
5
4
|
|
|
6
5
|
export type FormProps = React.PropsWithChildren<{
|
|
7
6
|
onSubmit: () => boolean | Promise<boolean>;
|
|
8
|
-
ref?: React.RefObject<FormActions>; // need to explicitly type for inference in <Form /> component
|
|
9
7
|
}>;
|
|
10
8
|
|
|
11
9
|
export type FormActions = {
|
|
@@ -18,8 +16,6 @@ export const Form = React.forwardRef<FormActions, FormProps>(
|
|
|
18
16
|
const refs = React.useRef<FormRef[]>([]);
|
|
19
17
|
const { setFocus } = useFocus();
|
|
20
18
|
|
|
21
|
-
const checks = __DEV__ ? useChecks?.() : undefined;
|
|
22
|
-
|
|
23
19
|
const focusField = React.useCallback(
|
|
24
20
|
(nextField?: Partial<FormRef> | undefined) => {
|
|
25
21
|
/**
|
|
@@ -35,14 +31,6 @@ export const Form = React.forwardRef<FormActions, FormProps>(
|
|
|
35
31
|
nextField?.hasFocusCallback &&
|
|
36
32
|
nextField?.isEditable;
|
|
37
33
|
|
|
38
|
-
__DEV__ &&
|
|
39
|
-
nextRefElement == null &&
|
|
40
|
-
checks?.logResult('nextRefElement', {
|
|
41
|
-
message:
|
|
42
|
-
'No next field found. Make sure you wrapped your form inside the <Form /> component',
|
|
43
|
-
rule: 'NO_UNDEFINED',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
34
|
if (callFocus) {
|
|
47
35
|
/**
|
|
48
36
|
* On some apps, if we call focus immediately and the field is already focused we lose the focus.
|
|
@@ -52,51 +40,33 @@ export const Form = React.forwardRef<FormActions, FormProps>(
|
|
|
52
40
|
|
|
53
41
|
setTimeout(() => {
|
|
54
42
|
nextRefElement?.current?.focus();
|
|
55
|
-
|
|
56
|
-
__DEV__ &&
|
|
57
|
-
nextRefElement &&
|
|
58
|
-
checks?.checkFocusTrap({
|
|
59
|
-
ref: nextRefElement,
|
|
60
|
-
shouldHaveFocus: true,
|
|
61
|
-
});
|
|
62
43
|
}, timeoutValue);
|
|
63
44
|
} else if (nextRefElement?.current) {
|
|
64
45
|
setFocus(nextRefElement?.current);
|
|
65
46
|
}
|
|
66
47
|
},
|
|
67
|
-
[
|
|
48
|
+
[setFocus]
|
|
68
49
|
);
|
|
69
50
|
|
|
70
|
-
const focusFieldAt =
|
|
71
|
-
(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const fieldWithError = refs.current[position];
|
|
51
|
+
const focusFieldAt = (position: number) => {
|
|
52
|
+
InteractionManager.runAfterInteractions(() => {
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
const fieldWithError = refs.current[position];
|
|
75
55
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
[focusField],
|
|
81
|
-
);
|
|
56
|
+
focusField(fieldWithError);
|
|
57
|
+
}, 0);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
82
60
|
|
|
83
|
-
const focusFirstInvalidField =
|
|
61
|
+
const focusFirstInvalidField = () => {
|
|
84
62
|
const fieldWithError = (refs.current || []).findIndex(
|
|
85
|
-
fieldRef => fieldRef.hasValidation && fieldRef.hasError
|
|
63
|
+
(fieldRef) => fieldRef.hasValidation && fieldRef.hasError
|
|
86
64
|
);
|
|
87
65
|
|
|
88
|
-
__DEV__ &&
|
|
89
|
-
fieldWithError == null &&
|
|
90
|
-
checks?.logResult('Form', {
|
|
91
|
-
message:
|
|
92
|
-
'The form validation has failed, but no component with error was found',
|
|
93
|
-
rule: 'NO_UNDEFINED',
|
|
94
|
-
});
|
|
95
|
-
|
|
96
66
|
focusFieldAt(fieldWithError);
|
|
97
|
-
}
|
|
67
|
+
};
|
|
98
68
|
|
|
99
|
-
const submitForm =
|
|
69
|
+
const submitForm = async () => {
|
|
100
70
|
const isValid = await onSubmit();
|
|
101
71
|
|
|
102
72
|
if (isValid) {
|
|
@@ -104,7 +74,7 @@ export const Form = React.forwardRef<FormActions, FormProps>(
|
|
|
104
74
|
}
|
|
105
75
|
|
|
106
76
|
focusFirstInvalidField();
|
|
107
|
-
}
|
|
77
|
+
};
|
|
108
78
|
|
|
109
79
|
React.useImperativeHandle(ref, () => ({
|
|
110
80
|
focusFirstInvalidField,
|
|
@@ -113,11 +83,12 @@ export const Form = React.forwardRef<FormActions, FormProps>(
|
|
|
113
83
|
|
|
114
84
|
return (
|
|
115
85
|
<FormContext.Provider
|
|
116
|
-
value={{ refs: refs.current, submitForm, focusField }}
|
|
86
|
+
value={{ refs: refs.current, submitForm, focusField }}
|
|
87
|
+
>
|
|
117
88
|
{children}
|
|
118
89
|
</FormContext.Provider>
|
|
119
90
|
);
|
|
120
|
-
}
|
|
91
|
+
}
|
|
121
92
|
);
|
|
122
93
|
|
|
123
94
|
export type FormContextValue = {
|
|
@@ -140,11 +111,11 @@ export const FormContext = React.createContext<FormContextValue | null>(null);
|
|
|
140
111
|
const DEFAULT_CONTEXT_VALUE: FormContextValue = {
|
|
141
112
|
refs: [],
|
|
142
113
|
submitForm: () => Promise.resolve(),
|
|
143
|
-
focusField: () => {},
|
|
114
|
+
focusField: () => { },
|
|
144
115
|
};
|
|
145
116
|
|
|
146
117
|
export const useForm = (
|
|
147
|
-
{ suppressError }: { suppressError?: boolean } = { suppressError: false }
|
|
118
|
+
{ suppressError }: { suppressError?: boolean } = { suppressError: false }
|
|
148
119
|
) => {
|
|
149
120
|
const context = React.useContext(FormContext);
|
|
150
121
|
|
|
@@ -155,12 +126,17 @@ export const useForm = (
|
|
|
155
126
|
if (!context) {
|
|
156
127
|
__DEV__ &&
|
|
157
128
|
console.error(
|
|
158
|
-
'Please wrap your form field inside the <Form /> component'
|
|
129
|
+
'Please wrap your form field inside the <Form /> component'
|
|
159
130
|
);
|
|
160
131
|
throw new Error(
|
|
161
|
-
'useForm must be used within a FormContextProvider, please wrap your form field inside the <Form /> component'
|
|
132
|
+
'useForm must be used within a FormContextProvider, please wrap your form field inside the <Form /> component'
|
|
162
133
|
);
|
|
163
134
|
}
|
|
164
135
|
|
|
165
136
|
return context;
|
|
166
137
|
};
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
const isFocused = (input: TextInput | undefined | null) => {
|
|
141
|
+
return input?.isFocused();
|
|
142
|
+
};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { render } from '@testing-library/react-native';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { Text } from 'react-native';
|
|
4
|
-
|
|
5
4
|
import * as UseFormField from '../hooks/useFormField';
|
|
6
5
|
import { FormField } from './FormField';
|
|
7
6
|
|
|
7
|
+
jest.mock('../hooks/useFormField', () => ({
|
|
8
|
+
useFormField: jest.fn(() => ({})),
|
|
9
|
+
}));
|
|
10
|
+
|
|
8
11
|
beforeEach(() => {
|
|
9
12
|
jest.clearAllMocks();
|
|
13
|
+
(UseFormField.useFormField as jest.Mock).mockReturnValue({});
|
|
10
14
|
});
|
|
11
15
|
|
|
12
16
|
describe('FormField', () => {
|
|
@@ -25,6 +29,18 @@ describe('FormField', () => {
|
|
|
25
29
|
});
|
|
26
30
|
});
|
|
27
31
|
|
|
32
|
+
it('renders children without accessible View when wrapInsideAccessibleView is false', () => {
|
|
33
|
+
const { toJSON } = render(
|
|
34
|
+
<FormField hasValidation={false} wrapInsideAccessibleView={false}>
|
|
35
|
+
<Text>Test</Text>
|
|
36
|
+
</FormField>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const json = toJSON() as any;
|
|
40
|
+
expect(json.type).toBe('TouchableWithoutFeedback');
|
|
41
|
+
expect(json.children[0].type).not.toBe('View');
|
|
42
|
+
});
|
|
43
|
+
|
|
28
44
|
it('wraps the children in a focusable View', () => {
|
|
29
45
|
const { toJSON } = render(
|
|
30
46
|
<FormField hasValidation={false}>
|
|
@@ -33,22 +49,22 @@ describe('FormField', () => {
|
|
|
33
49
|
);
|
|
34
50
|
|
|
35
51
|
expect(toJSON()).toMatchInlineSnapshot(`
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
onResponderRelease={[Function]}
|
|
44
|
-
onResponderTerminate={[Function]}
|
|
45
|
-
onResponderTerminationRequest={[Function]}
|
|
46
|
-
onStartShouldSetResponder={[Function]}
|
|
52
|
+
<TouchableWithoutFeedback
|
|
53
|
+
hasValidation={false}
|
|
54
|
+
ref={
|
|
55
|
+
{
|
|
56
|
+
"current": null,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
47
59
|
>
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
<View
|
|
61
|
+
accessible={true}
|
|
62
|
+
>
|
|
63
|
+
<Text>
|
|
64
|
+
Test
|
|
65
|
+
</Text>
|
|
66
|
+
</View>
|
|
67
|
+
</TouchableWithoutFeedback>
|
|
52
68
|
`);
|
|
53
69
|
});
|
|
54
70
|
});
|
|
@@ -1,71 +1,33 @@
|
|
|
1
|
-
import { render } from '@testing-library/react-native';
|
|
1
|
+
import { fireEvent, render } from '@testing-library/react-native';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { Pressable
|
|
4
|
-
|
|
3
|
+
import { Pressable } from 'react-native';
|
|
5
4
|
import { FormSubmit } from './FormSubmit';
|
|
5
|
+
import * as Form from './Form';
|
|
6
6
|
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
jest.clearAllMocks();
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
describe('FormSubmit', () => {
|
|
12
|
-
it('
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
busy={false}
|
|
17
|
-
testID="test-me"
|
|
18
|
-
/>,
|
|
12
|
+
it('provides onPress to its children render function', () => {
|
|
13
|
+
const submitForm = jest.fn().mockResolvedValue(undefined);
|
|
14
|
+
jest.spyOn(Form, 'useForm').mockImplementation(
|
|
15
|
+
() => ({ submitForm }) as any,
|
|
19
16
|
);
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
accessibilityRole: 'button',
|
|
25
|
-
accessibilityLabel: 'This is the label',
|
|
26
|
-
accessibilityState: {
|
|
27
|
-
busy: false,
|
|
28
|
-
},
|
|
29
|
-
busy: false,
|
|
30
|
-
accessible: true,
|
|
31
|
-
}),
|
|
32
|
-
);
|
|
33
|
-
});
|
|
18
|
+
const renderChildren = jest.fn(({ onPress }) => (
|
|
19
|
+
<Pressable testID="submit" onPress={onPress} />
|
|
20
|
+
));
|
|
34
21
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<FormSubmit
|
|
38
|
-
accessibilityLabel="This is the label"
|
|
39
|
-
busy={false}
|
|
40
|
-
testID="test-me">
|
|
41
|
-
<View testID="child" />
|
|
42
|
-
</FormSubmit>,
|
|
22
|
+
const { getByTestId, UNSAFE_getAllByType } = render(
|
|
23
|
+
<FormSubmit>{renderChildren}</FormSubmit>,
|
|
43
24
|
);
|
|
44
25
|
|
|
45
|
-
expect(
|
|
46
|
-
|
|
47
|
-
children: null,
|
|
48
|
-
importantForAccessibility: 'no',
|
|
49
|
-
testID: 'child',
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
jest.mock('../hooks/useFormField', () => {
|
|
55
|
-
return {
|
|
56
|
-
useFormField: jest.fn().mockReturnValue({}),
|
|
57
|
-
};
|
|
58
|
-
});
|
|
26
|
+
expect(renderChildren).toHaveBeenCalledTimes(1);
|
|
27
|
+
expect(UNSAFE_getAllByType(Pressable)).toHaveLength(1);
|
|
59
28
|
|
|
60
|
-
|
|
61
|
-
const originalModule = jest.requireActual('@react-native-ama/core');
|
|
29
|
+
fireEvent.press(getByTestId('submit'));
|
|
62
30
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
useAMAContext: () => {
|
|
66
|
-
return {
|
|
67
|
-
isScreenReaderEnabled: true,
|
|
68
|
-
};
|
|
69
|
-
},
|
|
70
|
-
};
|
|
31
|
+
expect(submitForm).toHaveBeenCalledTimes(1);
|
|
32
|
+
});
|
|
71
33
|
});
|
|
@@ -1,43 +1,16 @@
|
|
|
1
|
-
import { useAMAContext } from '@react-native-ama/core';
|
|
2
|
-
import { HideChildrenFromAccessibilityTree } from '@react-native-ama/core';
|
|
3
1
|
import React from 'react';
|
|
4
|
-
import { Pressable, PressableProps } from 'react-native';
|
|
5
|
-
|
|
6
2
|
import { useForm } from '../components/Form';
|
|
7
3
|
|
|
8
|
-
export type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
busy: boolean;
|
|
12
|
-
style?: PressableProps['style'];
|
|
13
|
-
testID?: string;
|
|
14
|
-
}>;
|
|
15
|
-
|
|
16
|
-
export const FormSubmit = (props: FormSubmitProps) => {
|
|
17
|
-
const { submitForm } = useForm();
|
|
18
|
-
const { isScreenReaderEnabled } = useAMAContext();
|
|
4
|
+
export type FormSubmitRenderProps = {
|
|
5
|
+
onPress: () => Promise<void>;
|
|
6
|
+
};
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
accessibilityRole="button"
|
|
23
|
-
pointerEvents="box-only" // prevent event propagation to children eg Pressable
|
|
24
|
-
{...props}
|
|
25
|
-
onPress={submitForm}>
|
|
26
|
-
{isScreenReaderEnabled ? (
|
|
27
|
-
<FormSubmitForScreenReader children={props.children} />
|
|
28
|
-
) : (
|
|
29
|
-
props.children
|
|
30
|
-
)}
|
|
31
|
-
</Pressable>
|
|
32
|
-
);
|
|
8
|
+
export type FormSubmitProps = {
|
|
9
|
+
children: (props: FormSubmitRenderProps) => React.ReactNode;
|
|
33
10
|
};
|
|
34
11
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<HideChildrenFromAccessibilityTree>
|
|
40
|
-
{children}
|
|
41
|
-
</HideChildrenFromAccessibilityTree>
|
|
42
|
-
);
|
|
12
|
+
export const FormSubmit = ({ children }: FormSubmitProps) => {
|
|
13
|
+
const { submitForm } = useForm();
|
|
14
|
+
|
|
15
|
+
return <>{children({ onPress: submitForm })}</>;
|
|
43
16
|
};
|