@react-native-ama/core 0.0.1 → 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 → components}/AMAProvider.js +1 -2
- package/dist/{core/src/index.js → index.js} +3 -3
- package/package.json +31 -6
- package/src/components/AMAProvider.d.ts +26 -0
- package/src/components/AMAProvider.integration.test.tsx +145 -0
- package/src/components/AMAProvider.js +147 -0
- package/src/components/AMAProvider.test.tsx +199 -0
- package/src/components/AMAProvider.tsx +229 -0
- package/src/components/AutofocusContainer.d.ts +9 -0
- package/src/components/AutofocusContainer.js +58 -0
- package/src/components/AutofocusContainer.test.tsx +69 -0
- package/src/components/AutofocusContainer.tsx +43 -0
- package/src/components/HideChildrenFromAccessibilityTree.d.ts +6 -0
- package/src/components/HideChildrenFromAccessibilityTree.js +40 -0
- package/src/components/HideChildrenFromAccessibilityTree.test.tsx +129 -0
- package/src/components/HideChildrenFromAccessibilityTree.tsx +55 -0
- package/src/hooks/useButtonChecks.d.ts +8 -0
- package/src/hooks/useButtonChecks.js +51 -0
- package/src/hooks/useButtonChecks.ts +73 -0
- package/src/hooks/useChecks.d.ts +15 -0
- package/src/hooks/useChecks.js +152 -0
- package/src/hooks/useChecks.test.ts +214 -0
- package/src/hooks/useChecks.ts +207 -0
- package/src/hooks/useFocus.d.ts +4 -0
- package/{dist/internal/src/utils/maybeGenerateStringFromElement.js → src/hooks/useFocus.js} +26 -22
- package/src/hooks/useFocus.test.ts +99 -0
- package/src/hooks/useFocus.ts +42 -0
- package/src/hooks/useTimedAction.android.test.ts +39 -0
- package/src/hooks/useTimedAction.d.ts +3 -0
- package/src/hooks/useTimedAction.ios.test.ts +46 -0
- package/src/hooks/useTimedAction.js +63 -0
- package/src/hooks/useTimedAction.ts +40 -0
- package/src/index.d.ts +7 -0
- package/src/index.js +20 -0
- package/src/index.ts +16 -0
- package/src/utils/numerify.test.ts +11 -0
- package/src/utils/numerify.ts +3 -0
- package/dist/internal/src/checks/checkAccessibilityRole.d.ts +0 -3
- package/dist/internal/src/checks/checkAccessibilityRole.js +0 -32
- package/dist/internal/src/checks/checkFocusTrap.d.ts +0 -8
- package/dist/internal/src/checks/checkFocusTrap.js +0 -34
- package/dist/internal/src/checks/checkForAccessibilityState.d.ts +0 -7
- package/dist/internal/src/checks/checkForAccessibilityState.js +0 -48
- package/dist/internal/src/checks/checkMinimumSize.d.ts +0 -3
- package/dist/internal/src/checks/checkMinimumSize.js +0 -29
- package/dist/internal/src/checks/contrastChecker.d.ts +0 -8
- package/dist/internal/src/checks/contrastChecker.js +0 -77
- package/dist/internal/src/checks/noUndefinedProperty.d.ts +0 -8
- package/dist/internal/src/checks/noUndefinedProperty.js +0 -14
- package/dist/internal/src/checks/uppercaseChecker.d.ts +0 -9
- package/dist/internal/src/checks/uppercaseChecker.js +0 -18
- package/dist/internal/src/checks/uppercaseStringChecker.d.ts +0 -8
- package/dist/internal/src/checks/uppercaseStringChecker.js +0 -29
- package/dist/internal/src/index.d.ts +0 -20
- package/dist/internal/src/index.js +0 -56
- package/dist/internal/src/types.d.ts +0 -17
- package/dist/internal/src/types.js +0 -2
- package/dist/internal/src/utils/applyStyle.d.ts +0 -10
- package/dist/internal/src/utils/applyStyle.js +0 -33
- package/dist/internal/src/utils/constants.d.ts +0 -3
- package/dist/internal/src/utils/constants.js +0 -32
- package/dist/internal/src/utils/error.style.d.ts +0 -6
- package/dist/internal/src/utils/error.style.js +0 -11
- package/dist/internal/src/utils/generateAccessibilityStateFromProp.d.ts +0 -9
- package/dist/internal/src/utils/generateAccessibilityStateFromProp.js +0 -35
- package/dist/internal/src/utils/getPropertyFromStyle.d.ts +0 -2
- package/dist/internal/src/utils/getPropertyFromStyle.js +0 -10
- package/dist/internal/src/utils/interpolateAnimationStates.d.ts +0 -2
- package/dist/internal/src/utils/interpolateAnimationStates.js +0 -43
- package/dist/internal/src/utils/isFocused.d.ts +0 -2
- package/dist/internal/src/utils/isFocused.js +0 -7
- package/dist/internal/src/utils/logger.d.ts +0 -16
- package/dist/internal/src/utils/logger.js +0 -53
- package/dist/internal/src/utils/logger.rules.d.ts +0 -15
- package/dist/internal/src/utils/logger.rules.js +0 -74
- package/dist/internal/src/utils/maybeGenerateStringFromElement.d.ts +0 -3
- package/dist/internal/src/utils/minimumTouchableSize.d.ts +0 -3
- package/dist/internal/src/utils/minimumTouchableSize.js +0 -9
- package/dist/{core/src/components → components}/AMAProvider.d.ts +0 -0
- package/dist/{core/src/components → components}/AutofocusContainer.d.ts +0 -0
- package/dist/{core/src/components → components}/AutofocusContainer.js +0 -0
- package/dist/{core/src/components → components}/HideChildrenFromAccessibilityTree.d.ts +0 -0
- package/dist/{core/src/components → components}/HideChildrenFromAccessibilityTree.js +0 -0
- package/dist/{core/src/hooks → hooks}/useButtonChecks.d.ts +0 -0
- package/dist/{core/src/hooks → hooks}/useButtonChecks.js +0 -0
- package/dist/{core/src/hooks → hooks}/useChecks.d.ts +0 -0
- package/dist/{core/src/hooks → hooks}/useChecks.js +0 -0
- package/dist/{core/src/hooks → hooks}/useFocus.d.ts +0 -0
- package/dist/{core/src/hooks → hooks}/useFocus.js +0 -0
- package/dist/{core/src/hooks → hooks}/useTimedAction.d.ts +0 -0
- package/dist/{core/src/hooks → hooks}/useTimedAction.js +0 -0
- package/dist/{core/src/index.d.ts → index.d.ts} +1 -1
- /package/dist/{core/src/utils → utils}/numerify.d.ts +0 -0
- /package/dist/{core/src/utils → utils}/numerify.js +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ERROR_STYLE,
|
|
3
|
+
LogParams,
|
|
4
|
+
checkAccessibilityRole as checkAccessibilityRoleImplementation,
|
|
5
|
+
checkFocusTrap as checkFocusTrapImplementation,
|
|
6
|
+
checkForAccessibilityState,
|
|
7
|
+
checkMinimumSize as checkMinimumSizeImplementation,
|
|
8
|
+
contrastChecker as contrastCheckerImplementation,
|
|
9
|
+
getRuleAction,
|
|
10
|
+
logFailure,
|
|
11
|
+
noUndefinedProperty as noUndefinedPropertyImplementation,
|
|
12
|
+
uppercaseStringChecker as noUppercaseStringCheckerImplementation,
|
|
13
|
+
uppercaseChecker as uppercaseCheckerImplementation,
|
|
14
|
+
type CheckFocusTrap,
|
|
15
|
+
type CheckForAccessibilityState,
|
|
16
|
+
type ContrastChecker,
|
|
17
|
+
type NoUndefinedProperty,
|
|
18
|
+
type UppercaseChecker,
|
|
19
|
+
type UppercaseStringChecker,
|
|
20
|
+
} from '@react-native-ama/internal';
|
|
21
|
+
import * as React from 'react';
|
|
22
|
+
import {
|
|
23
|
+
AccessibilityRole,
|
|
24
|
+
InteractionManager,
|
|
25
|
+
LayoutChangeEvent,
|
|
26
|
+
} from 'react-native';
|
|
27
|
+
|
|
28
|
+
import { isDevContextValue, useAMAContext } from '../components/AMAProvider';
|
|
29
|
+
|
|
30
|
+
export const useChecks = () => {
|
|
31
|
+
const context = useAMAContext();
|
|
32
|
+
|
|
33
|
+
if (!isDevContextValue(context)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Because the AMA Error view cannot be removed by default, this hacky solution
|
|
39
|
+
* allows the dev to hide it if all the issues have been solved.
|
|
40
|
+
*/
|
|
41
|
+
const fakeRandom = React.useRef('' + Date.now() + Math.random() * 42);
|
|
42
|
+
const hasErrors = React.useRef(false);
|
|
43
|
+
const failedTests = React.useRef<string[]>([]);
|
|
44
|
+
const shouldCheckLayout = React.useRef(true);
|
|
45
|
+
const layoutCheckTimeout = React.useRef<NodeJS.Timeout>();
|
|
46
|
+
const [minimumSizeFailed, setMinimumSizeFailed] = React.useState(false);
|
|
47
|
+
const [debugStyle, setDebugStyle] = React.useState<any>({});
|
|
48
|
+
|
|
49
|
+
const { trackError, removeError } = context;
|
|
50
|
+
|
|
51
|
+
const logResult = (
|
|
52
|
+
name: string,
|
|
53
|
+
result: LogParams | LogParams[] | null,
|
|
54
|
+
): Record<string, any> => {
|
|
55
|
+
const index = failedTests.current.indexOf(name);
|
|
56
|
+
|
|
57
|
+
if (result === null) {
|
|
58
|
+
if (index >= 0 && hasErrors.current) {
|
|
59
|
+
failedTests.current.splice(index);
|
|
60
|
+
|
|
61
|
+
hasErrors.current = false;
|
|
62
|
+
|
|
63
|
+
InteractionManager.runAfterInteractions(() => {
|
|
64
|
+
removeError(fakeRandom.current);
|
|
65
|
+
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
setDebugStyle({});
|
|
68
|
+
}, 100);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const results = Array.isArray(result) ? result : [result];
|
|
74
|
+
|
|
75
|
+
results.forEach(logParam => {
|
|
76
|
+
if (logParam === null) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const action = getRuleAction?.(logParam.rule);
|
|
81
|
+
const hasFailed = action === 'MUST_NOT' || action === 'MUST';
|
|
82
|
+
|
|
83
|
+
if (index < 0) {
|
|
84
|
+
// @ts-ignore
|
|
85
|
+
logFailure?.({ action, ...logParam });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!hasFailed) {
|
|
89
|
+
return;
|
|
90
|
+
} else if (index >= 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
failedTests.current.push(name);
|
|
95
|
+
|
|
96
|
+
InteractionManager.runAfterInteractions(() => {
|
|
97
|
+
trackError(fakeRandom.current);
|
|
98
|
+
|
|
99
|
+
setDebugStyle(ERROR_STYLE);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
hasErrors.current = true;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
return hasErrors.current ? ERROR_STYLE : {};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const noUndefinedProperty = <T>(params: NoUndefinedProperty<T>) =>
|
|
110
|
+
logResult(
|
|
111
|
+
`noUndefinedProperty ${params.property as string}`,
|
|
112
|
+
noUndefinedPropertyImplementation(params),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const contrastChecker = (params: ContrastChecker) =>
|
|
116
|
+
logResult('contrastChecker', contrastCheckerImplementation(params));
|
|
117
|
+
|
|
118
|
+
const checkMinimumSize = (params: LayoutChangeEvent) => {
|
|
119
|
+
return logResult(
|
|
120
|
+
'checkMinimumSize',
|
|
121
|
+
checkMinimumSizeImplementation(params),
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const noUppercaseStringChecker = (params: UppercaseStringChecker) =>
|
|
126
|
+
logResult(
|
|
127
|
+
'accessibilityLabelChecker',
|
|
128
|
+
noUppercaseStringCheckerImplementation(params),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const uppercaseChecker = (params: UppercaseChecker) =>
|
|
132
|
+
logResult('uppercaseChecker', uppercaseCheckerImplementation(params));
|
|
133
|
+
|
|
134
|
+
const checkFocusTrap = (params: CheckFocusTrap) => {
|
|
135
|
+
checkFocusTrapImplementation(params).then(result => {
|
|
136
|
+
logResult('checkFocusTrap', result);
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const checkAccessibilityRole = (param: AccessibilityRole) => {
|
|
141
|
+
logResult(
|
|
142
|
+
'checkAccessibilityRole',
|
|
143
|
+
checkAccessibilityRoleImplementation(param),
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const checkCompatibleAccessibilityState = (props: Record<string, any>) => {
|
|
148
|
+
const amaStates = ['checked', 'selected'];
|
|
149
|
+
|
|
150
|
+
const params: CheckForAccessibilityState = {
|
|
151
|
+
accessibilityRole: props.accessibilityRole,
|
|
152
|
+
accessibilityState: props?.accessibilityState,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
amaStates.forEach(amaState => {
|
|
156
|
+
if (amaState in props) {
|
|
157
|
+
// @ts-ignore
|
|
158
|
+
params[amaState] = props[amaState];
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
logResult(
|
|
163
|
+
'checkCompatibleAccessibilityState',
|
|
164
|
+
checkForAccessibilityState(params),
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
169
|
+
/**
|
|
170
|
+
* When the check fails there are situation when adding a border makes the
|
|
171
|
+
* component meet the minimum size requirement, and this causes a loop as the
|
|
172
|
+
* state is update continuously between true and false.
|
|
173
|
+
* To "avoid" that we wait at least 100ms before checking the size again, as
|
|
174
|
+
* this gives the dev time to hot-reload the changes.
|
|
175
|
+
*/
|
|
176
|
+
if (!shouldCheckLayout.current) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
shouldCheckLayout.current = false;
|
|
181
|
+
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
clearTimeout(layoutCheckTimeout.current);
|
|
184
|
+
|
|
185
|
+
layoutCheckTimeout.current = setTimeout(() => {
|
|
186
|
+
shouldCheckLayout.current = true;
|
|
187
|
+
}, 1000);
|
|
188
|
+
|
|
189
|
+
const result = checkMinimumSize(event);
|
|
190
|
+
|
|
191
|
+
setMinimumSizeFailed(Object.keys(result).length > 0);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
logResult,
|
|
196
|
+
noUndefinedProperty,
|
|
197
|
+
contrastChecker,
|
|
198
|
+
onLayout,
|
|
199
|
+
noUppercaseStringChecker,
|
|
200
|
+
uppercaseChecker,
|
|
201
|
+
checkFocusTrap,
|
|
202
|
+
minimumSizeFailed,
|
|
203
|
+
checkCompatibleAccessibilityState,
|
|
204
|
+
checkAccessibilityRole,
|
|
205
|
+
debugStyle,
|
|
206
|
+
};
|
|
207
|
+
};
|
|
@@ -23,29 +23,33 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.
|
|
26
|
+
exports.useFocus = void 0;
|
|
27
|
+
const internal_1 = require("@react-native-ama/internal");
|
|
27
28
|
const React = __importStar(require("react"));
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return React.Children.map(component, child => {
|
|
42
|
-
if (!React.isValidElement(child)) {
|
|
43
|
-
return '';
|
|
29
|
+
const react_native_1 = require("react-native");
|
|
30
|
+
const useFocus = (refComponent) => {
|
|
31
|
+
const setFocus = React.useCallback((component) => {
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
const elementId = (0, react_native_1.findNodeHandle)(component);
|
|
34
|
+
if (elementId) {
|
|
35
|
+
react_native_1.AccessibilityInfo.setAccessibilityFocus(elementId);
|
|
36
|
+
react_native_1.AccessibilityInfo.setAccessibilityFocus(elementId);
|
|
37
|
+
}
|
|
38
|
+
else if (__DEV__) {
|
|
39
|
+
console.warn(
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
`${internal_1.SHELL_COLORS.BG_RED}AMA.${internal_1.SHELL_COLORS.RESET} ${internal_1.SHELL_COLORS.BLUE}useFocus${internal_1.SHELL_COLORS.RESET}: ${internal_1.SHELL_COLORS.YELLOW}Ref element not found${internal_1.SHELL_COLORS.RESET}`);
|
|
44
42
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
}, []);
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (!refComponent) {
|
|
46
|
+
return;
|
|
48
47
|
}
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
setFocus(refComponent.current);
|
|
49
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
|
+
}, [refComponent]);
|
|
51
|
+
return {
|
|
52
|
+
setFocus,
|
|
53
|
+
};
|
|
51
54
|
};
|
|
55
|
+
exports.useFocus = useFocus;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { SHELL_COLORS } from '@react-native-ama/internal';
|
|
2
|
+
import { renderHook } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import { useFocus } from './useFocus';
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
jest.clearAllMocks();
|
|
8
|
+
|
|
9
|
+
jest.spyOn(console, 'warn').mockImplementation();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
let findNodeHandleMock: jest.Mock;
|
|
13
|
+
let setAccessibilityFocusMock: jest.Mock;
|
|
14
|
+
|
|
15
|
+
describe('useFocus', () => {
|
|
16
|
+
it.each([null, undefined])('does nothing if the Ref is %s', refElement => {
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
renderHook(() => useFocus(refElement));
|
|
19
|
+
|
|
20
|
+
expect(findNodeHandleMock).not.toHaveBeenCalled();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('call setAccessibilityFocus on the given Ref Component', () => {
|
|
24
|
+
findNodeHandleMock.mockReturnValue('element-id');
|
|
25
|
+
|
|
26
|
+
renderHook(() => useFocus({ current: 'test-element' }));
|
|
27
|
+
|
|
28
|
+
expect(findNodeHandleMock).toHaveBeenCalledWith('test-element');
|
|
29
|
+
expect(setAccessibilityFocusMock).toHaveBeenNthCalledWith(1, 'element-id');
|
|
30
|
+
expect(setAccessibilityFocusMock).toHaveBeenNthCalledWith(2, 'element-id');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('Given the element has not been found', () => {
|
|
34
|
+
beforeEach(function () {
|
|
35
|
+
findNodeHandleMock.mockReturnValue(null);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('does not call setAccessibilityFocus', () => {
|
|
39
|
+
renderHook(() => useFocus({ current: 'test-element' }));
|
|
40
|
+
|
|
41
|
+
expect(setAccessibilityFocusMock).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('console.warn if __DEV__ is true', () => {
|
|
45
|
+
const spy = jest.spyOn(console, 'warn').mockImplementation();
|
|
46
|
+
|
|
47
|
+
renderHook(() => useFocus({ current: 'test-element' }));
|
|
48
|
+
|
|
49
|
+
expect(spy).toHaveBeenCalledWith(
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
`${SHELL_COLORS.BG_RED}AMA.${SHELL_COLORS.RESET} ${SHELL_COLORS.BLUE}useFocus${SHELL_COLORS.RESET}: ${SHELL_COLORS.YELLOW}Ref element not found${SHELL_COLORS.RESET}`,
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('does not log anything if __DEV__ is false', () => {
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
global.__DEV__ = false;
|
|
58
|
+
|
|
59
|
+
const spy = jest.spyOn(console, 'warn');
|
|
60
|
+
|
|
61
|
+
renderHook(() => useFocus({ current: 'test-element' }));
|
|
62
|
+
|
|
63
|
+
expect(spy).not.toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('setFocus calls setAccessibilityFocus on the given component', () => {
|
|
68
|
+
findNodeHandleMock.mockReturnValue('test-focus-id');
|
|
69
|
+
|
|
70
|
+
const { result } = renderHook(() => useFocus());
|
|
71
|
+
|
|
72
|
+
result.current.setFocus('component' as any);
|
|
73
|
+
|
|
74
|
+
expect(findNodeHandleMock).toHaveBeenCalledWith('component');
|
|
75
|
+
expect(setAccessibilityFocusMock).toHaveBeenNthCalledWith(
|
|
76
|
+
1,
|
|
77
|
+
'test-focus-id',
|
|
78
|
+
);
|
|
79
|
+
expect(setAccessibilityFocusMock).toHaveBeenNthCalledWith(
|
|
80
|
+
2,
|
|
81
|
+
'test-focus-id',
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function mockReactNative() {
|
|
87
|
+
findNodeHandleMock = jest.fn();
|
|
88
|
+
setAccessibilityFocusMock = jest.fn();
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
findNodeHandle: findNodeHandleMock,
|
|
92
|
+
AccessibilityInfo: {
|
|
93
|
+
setAccessibilityFocus: setAccessibilityFocusMock,
|
|
94
|
+
},
|
|
95
|
+
Platform: {},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
jest.mock('react-native', () => mockReactNative());
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SHELL_COLORS } from '@react-native-ama/internal';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { AccessibilityInfo, findNodeHandle } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export const useFocus = (refComponent?: React.RefObject<any>) => {
|
|
6
|
+
const setFocus = React.useCallback(
|
|
7
|
+
(
|
|
8
|
+
component:
|
|
9
|
+
| null
|
|
10
|
+
| number
|
|
11
|
+
| React.Component<any, any>
|
|
12
|
+
| React.ComponentClass<any>,
|
|
13
|
+
) => {
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
const elementId = findNodeHandle(component);
|
|
16
|
+
|
|
17
|
+
if (elementId) {
|
|
18
|
+
AccessibilityInfo.setAccessibilityFocus(elementId);
|
|
19
|
+
AccessibilityInfo.setAccessibilityFocus(elementId);
|
|
20
|
+
} else if (__DEV__) {
|
|
21
|
+
console.warn(
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
`${SHELL_COLORS.BG_RED}AMA.${SHELL_COLORS.RESET} ${SHELL_COLORS.BLUE}useFocus${SHELL_COLORS.RESET}: ${SHELL_COLORS.YELLOW}Ref element not found${SHELL_COLORS.RESET}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
[],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (!refComponent) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setFocus(refComponent.current);
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
}, [refComponent]);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
setFocus,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react-native';
|
|
2
|
+
import { AccessibilityInfo, Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import * as AMAProvider from '../components/AMAProvider';
|
|
5
|
+
import { useTimedAction } from './useTimedAction';
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
|
|
10
|
+
Platform.OS = 'android';
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('useTimedAction', () => {
|
|
14
|
+
it.each([true, false])(
|
|
15
|
+
'onTimeout uses the value provided by getRecommendedTimeoutMillis for the timeout',
|
|
16
|
+
async isScreenReaderEnabled => {
|
|
17
|
+
const callback = jest.fn();
|
|
18
|
+
|
|
19
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
20
|
+
isScreenReaderEnabled,
|
|
21
|
+
} as any);
|
|
22
|
+
|
|
23
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
24
|
+
|
|
25
|
+
const getRecommendedTimeoutMillis = jest
|
|
26
|
+
.spyOn(AccessibilityInfo, 'getRecommendedTimeoutMillis')
|
|
27
|
+
.mockResolvedValue(142);
|
|
28
|
+
|
|
29
|
+
const { result } = renderHook(() => useTimedAction());
|
|
30
|
+
|
|
31
|
+
await result.current.onTimeout(callback, 42);
|
|
32
|
+
|
|
33
|
+
expect(getRecommendedTimeoutMillis).toHaveBeenCalledWith(42);
|
|
34
|
+
expect(setTimeoutSpy).toHaveBeenCalledWith(callback, 142);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
jest.mock('../components/AMAProvider');
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react-native';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import * as AMAProvider from '../components/AMAProvider';
|
|
5
|
+
import { useTimedAction } from './useTimedAction';
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
|
|
10
|
+
Platform.OS = 'ios';
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('useTimedAction', () => {
|
|
14
|
+
it('onTimeout executes the callback with the given timeout if the screen reader is off', async () => {
|
|
15
|
+
const callback = jest.fn();
|
|
16
|
+
//TODO: AMAProvider does not export useAMAContext
|
|
17
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
18
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
19
|
+
isScreenReaderEnabled: false,
|
|
20
|
+
} as any);
|
|
21
|
+
|
|
22
|
+
const { result } = renderHook(() => useTimedAction());
|
|
23
|
+
|
|
24
|
+
setTimeoutSpy.mockClear();
|
|
25
|
+
await result.current.onTimeout(callback, 42);
|
|
26
|
+
|
|
27
|
+
expect(setTimeoutSpy).toHaveBeenCalledWith(callback, 42);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('onTimeout does not executes the callback when the screen reader is off', async () => {
|
|
31
|
+
const callback = jest.fn();
|
|
32
|
+
|
|
33
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
34
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
35
|
+
isScreenReaderEnabled: true,
|
|
36
|
+
} as any);
|
|
37
|
+
|
|
38
|
+
const { result } = renderHook(() => useTimedAction());
|
|
39
|
+
|
|
40
|
+
setTimeoutSpy.mockClear();
|
|
41
|
+
|
|
42
|
+
await result.current.onTimeout(callback, 42);
|
|
43
|
+
|
|
44
|
+
expect(setTimeoutSpy).not.toHaveBeenCalled();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.useTimedAction = void 0;
|
|
36
|
+
const React = __importStar(require("react"));
|
|
37
|
+
const react_native_1 = require("react-native");
|
|
38
|
+
const AMAProvider_1 = require("../components/AMAProvider");
|
|
39
|
+
const useTimedAction = () => {
|
|
40
|
+
const { isScreenReaderEnabled } = (0, AMAProvider_1.useAMAContext)();
|
|
41
|
+
const onTimeout = React.useCallback((callback, milliseconds) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
if (isScreenReaderEnabled && react_native_1.Platform.OS === 'ios') {
|
|
43
|
+
return new Promise(resolve => {
|
|
44
|
+
resolve(null);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const timeout = yield getRecommendedTimeoutMillis(milliseconds);
|
|
48
|
+
return setTimeout(callback, timeout);
|
|
49
|
+
}), [isScreenReaderEnabled]);
|
|
50
|
+
return {
|
|
51
|
+
onTimeout,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
exports.useTimedAction = useTimedAction;
|
|
55
|
+
function getRecommendedTimeoutMillis(milliseconds) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
return react_native_1.Platform.OS === 'android'
|
|
58
|
+
? yield react_native_1.AccessibilityInfo.getRecommendedTimeoutMillis(milliseconds)
|
|
59
|
+
: new Promise(resolve => {
|
|
60
|
+
resolve(milliseconds);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { AccessibilityInfo, Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useAMAContext } from '../components/AMAProvider';
|
|
5
|
+
|
|
6
|
+
export const useTimedAction = () => {
|
|
7
|
+
const { isScreenReaderEnabled } = useAMAContext();
|
|
8
|
+
|
|
9
|
+
const onTimeout = React.useCallback(
|
|
10
|
+
async (
|
|
11
|
+
callback: () => void,
|
|
12
|
+
milliseconds: number,
|
|
13
|
+
): Promise<null | ReturnType<typeof setTimeout>> => {
|
|
14
|
+
if (isScreenReaderEnabled && Platform.OS === 'ios') {
|
|
15
|
+
return new Promise<null>(resolve => {
|
|
16
|
+
resolve(null);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const timeout = await getRecommendedTimeoutMillis(milliseconds);
|
|
21
|
+
|
|
22
|
+
return setTimeout(callback, timeout);
|
|
23
|
+
},
|
|
24
|
+
[isScreenReaderEnabled],
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
onTimeout,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function getRecommendedTimeoutMillis(
|
|
33
|
+
milliseconds: number,
|
|
34
|
+
): Promise<number> {
|
|
35
|
+
return Platform.OS === 'android'
|
|
36
|
+
? await AccessibilityInfo.getRecommendedTimeoutMillis(milliseconds)
|
|
37
|
+
: new Promise(resolve => {
|
|
38
|
+
resolve(milliseconds);
|
|
39
|
+
});
|
|
40
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { AutofocusContainer } from './components/AutofocusContainer';
|
|
2
|
+
export { AMAProvider, useAMAContext, type AMAContextValue, type AMADevContextValue, type AMAProdContextValue, } from './components/AMAProvider';
|
|
3
|
+
export { HideChildrenFromAccessibilityTree } from './components/HideChildrenFromAccessibilityTree';
|
|
4
|
+
export { useButtonChecks } from './hooks/useButtonChecks';
|
|
5
|
+
export { useChecks } from './hooks/useChecks';
|
|
6
|
+
export { useFocus } from './hooks/useFocus';
|
|
7
|
+
export { useTimedAction } from './hooks/useTimedAction';
|
package/src/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTimedAction = exports.useFocus = exports.useChecks = exports.useButtonChecks = exports.HideChildrenFromAccessibilityTree = exports.useAMAContext = exports.AMAProvider = exports.AutofocusContainer = void 0;
|
|
4
|
+
// Components
|
|
5
|
+
var AutofocusContainer_1 = require("./components/AutofocusContainer");
|
|
6
|
+
Object.defineProperty(exports, "AutofocusContainer", { enumerable: true, get: function () { return AutofocusContainer_1.AutofocusContainer; } });
|
|
7
|
+
var AMAProvider_1 = require("./components/AMAProvider");
|
|
8
|
+
Object.defineProperty(exports, "AMAProvider", { enumerable: true, get: function () { return AMAProvider_1.AMAProvider; } });
|
|
9
|
+
Object.defineProperty(exports, "useAMAContext", { enumerable: true, get: function () { return AMAProvider_1.useAMAContext; } });
|
|
10
|
+
var HideChildrenFromAccessibilityTree_1 = require("./components/HideChildrenFromAccessibilityTree");
|
|
11
|
+
Object.defineProperty(exports, "HideChildrenFromAccessibilityTree", { enumerable: true, get: function () { return HideChildrenFromAccessibilityTree_1.HideChildrenFromAccessibilityTree; } });
|
|
12
|
+
// Hooks
|
|
13
|
+
var useButtonChecks_1 = require("./hooks/useButtonChecks");
|
|
14
|
+
Object.defineProperty(exports, "useButtonChecks", { enumerable: true, get: function () { return useButtonChecks_1.useButtonChecks; } });
|
|
15
|
+
var useChecks_1 = require("./hooks/useChecks");
|
|
16
|
+
Object.defineProperty(exports, "useChecks", { enumerable: true, get: function () { return useChecks_1.useChecks; } });
|
|
17
|
+
var useFocus_1 = require("./hooks/useFocus");
|
|
18
|
+
Object.defineProperty(exports, "useFocus", { enumerable: true, get: function () { return useFocus_1.useFocus; } });
|
|
19
|
+
var useTimedAction_1 = require("./hooks/useTimedAction");
|
|
20
|
+
Object.defineProperty(exports, "useTimedAction", { enumerable: true, get: function () { return useTimedAction_1.useTimedAction; } });
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { AutofocusContainer } from './components/AutofocusContainer';
|
|
3
|
+
export {
|
|
4
|
+
AMAProvider,
|
|
5
|
+
useAMAContext,
|
|
6
|
+
type AMAContextValue,
|
|
7
|
+
type AMADevContextValue,
|
|
8
|
+
type AMAProdContextValue,
|
|
9
|
+
} from './components/AMAProvider';
|
|
10
|
+
export { HideChildrenFromAccessibilityTree } from './components/HideChildrenFromAccessibilityTree';
|
|
11
|
+
|
|
12
|
+
// Hooks
|
|
13
|
+
export { useButtonChecks } from './hooks/useButtonChecks';
|
|
14
|
+
export { useChecks } from './hooks/useChecks';
|
|
15
|
+
export { useFocus } from './hooks/useFocus';
|
|
16
|
+
export { useTimedAction } from './hooks/useTimedAction';
|