@react-native-ama/core 0.0.1 → 0.0.2
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/package.json +2 -2
- package/src/components/AMAProvider.integration.test.tsx +145 -0
- package/src/components/AMAProvider.test.tsx +199 -0
- package/src/components/AMAProvider.tsx +231 -0
- package/src/components/AutofocusContainer.test.tsx +69 -0
- package/src/components/AutofocusContainer.tsx +43 -0
- package/src/components/HideChildrenFromAccessibilityTree.test.tsx +128 -0
- package/src/components/HideChildrenFromAccessibilityTree.tsx +55 -0
- package/src/hooks/useButtonChecks.ts +73 -0
- package/src/hooks/useChecks.test.ts +214 -0
- package/src/hooks/useChecks.ts +207 -0
- package/src/hooks/useFocus.test.ts +98 -0
- package/src/hooks/useFocus.ts +42 -0
- package/src/hooks/useTimedAction.android.test.ts +39 -0
- package/src/hooks/useTimedAction.ios.test.ts +46 -0
- package/src/hooks/useTimedAction.ts +40 -0
- package/src/index.ts +16 -0
- package/src/utils/numerify.test.ts +11 -0
- package/src/utils/numerify.ts +3 -0
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
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,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.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { AutofocusContainer } from './components/AutofocusContainer';
|
|
3
|
+
export { HideChildrenFromAccessibilityTree } from './components/HideChildrenFromAccessibilityTree';
|
|
4
|
+
export {
|
|
5
|
+
AMAProvider,
|
|
6
|
+
useAMAContext,
|
|
7
|
+
type AMAContextValue,
|
|
8
|
+
type AMADevContextValue,
|
|
9
|
+
type AMAProdContextValue,
|
|
10
|
+
} from './components/AMAProvider';
|
|
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';
|