@react-native-ama/core 1.2.1 → 2.0.0-beta.1
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/ReactNativeAma.types.d.ts +66 -0
- package/dist/ReactNativeAma.types.js +2 -0
- package/dist/ReactNativeAmaModule.d.ts +11 -0
- package/dist/ReactNativeAmaModule.js +5 -0
- package/dist/ReactNativeAmaModule.web.d.ts +9 -0
- package/dist/ReactNativeAmaModule.web.js +27 -0
- package/dist/ReactNativeAmaView.d.ts +3 -0
- package/dist/ReactNativeAmaView.js +32 -0
- package/dist/ReactNativeAmaView.web.d.ts +3 -0
- package/dist/ReactNativeAmaView.web.js +34 -0
- package/dist/{src/components → components}/AMAProvider.d.ts +4 -2
- package/dist/{src/components → components}/AMAProvider.js +19 -45
- package/dist/{src/components → components}/AutofocusContainer.d.ts +2 -1
- package/dist/{src/components → components}/HideChildrenFromAccessibilityTree.d.ts +1 -1
- package/dist/{src/components → components}/HideChildrenFromAccessibilityTree.js +7 -5
- package/dist/{src/hooks → hooks}/useFocus.js +10 -3
- package/dist/{src/index.d.ts → index.d.ts} +3 -2
- package/dist/index.js +38 -0
- package/dist/internals/checks/checkAriaLabel.d.ts +3 -0
- package/dist/internals/checks/checkAriaLabel.js +15 -0
- package/dist/internals/checks/checkAriaRole.d.ts +3 -0
- package/dist/internals/checks/checkAriaRole.js +48 -0
- package/dist/internals/checks/checkContrast.d.ts +3 -0
- package/dist/internals/checks/checkContrast.js +84 -0
- package/dist/internals/checks/checkImageAlt.d.ts +3 -0
- package/dist/internals/checks/checkImageAlt.js +20 -0
- package/dist/internals/checks/checkIsUppercase.d.ts +8 -0
- package/dist/internals/checks/checkIsUppercase.js +30 -0
- package/dist/internals/checks/checkLongNumber.d.ts +3 -0
- package/dist/internals/checks/checkLongNumber.js +31 -0
- package/dist/internals/checks/checkMinimumSize.d.ts +3 -0
- package/dist/internals/checks/checkMinimumSize.js +22 -0
- package/dist/internals/checks/checkTextInput.d.ts +3 -0
- package/dist/internals/checks/checkTextInput.js +51 -0
- package/dist/internals/checks/performChecks.d.ts +3 -0
- package/dist/internals/checks/performChecks.js +23 -0
- package/dist/internals/components/AMAErrorOverlay.d.ts +5 -0
- package/dist/internals/components/AMAErrorOverlay.js +334 -0
- package/dist/internals/components/AMARuleError.d.ts +7 -0
- package/dist/internals/components/AMARuleError.js +161 -0
- package/dist/internals/config.d.ts +23 -0
- package/dist/internals/config.js +33 -0
- package/dist/internals/types.d.ts +9 -0
- package/dist/internals/types.js +2 -0
- package/dist/internals/useAMADev.d.ts +8 -0
- package/dist/internals/useAMADev.dev.d.ts +2 -0
- package/dist/internals/useAMADev.dev.js +110 -0
- package/dist/internals/useAMADev.js +6 -0
- package/dist/internals/useAMADevNodeChecks.dev.d.ts +16 -0
- package/dist/internals/useAMADevNodeChecks.dev.js +115 -0
- package/dist/internals/useAMADevUiInteraction.dev.d.ts +7 -0
- package/dist/internals/useAMADevUiInteraction.dev.js +100 -0
- package/dist/internals/utils/amaClearHighlight.d.ts +2 -0
- package/dist/internals/utils/amaClearHighlight.js +12 -0
- package/dist/internals/utils/amaHighlightComponent.d.ts +3 -0
- package/dist/internals/utils/amaHighlightComponent.js +51 -0
- package/dist/internals/utils/constants.d.ts +3 -0
- package/dist/internals/utils/constants.js +32 -0
- package/dist/internals/utils/getErrorColor.d.ts +2 -0
- package/dist/internals/utils/getErrorColor.js +10 -0
- package/dist/internals/utils/getRuleAction.d.ts +2 -0
- package/dist/internals/utils/getRuleAction.js +40 -0
- package/dist/internals/utils/getRuleErrorInfo.d.ts +7 -0
- package/dist/internals/utils/getRuleErrorInfo.js +19 -0
- package/dist/internals/utils/ignoreContrastCheck.d.ts +1 -0
- package/dist/internals/utils/ignoreContrastCheck.js +15 -0
- package/dist/internals/utils/interpolateAnimationStates.d.ts +2 -0
- package/dist/internals/utils/interpolateAnimationStates.js +43 -0
- package/dist/internals/utils/isA11yLabelAllowed.d.ts +1 -0
- package/dist/internals/utils/isA11yLabelAllowed.js +14 -0
- package/dist/internals/utils/isRuleDisabled.d.ts +2 -0
- package/dist/internals/utils/isRuleDisabled.js +10 -0
- package/dist/internals/utils/logError.d.ts +2 -0
- package/dist/internals/utils/logError.js +36 -0
- package/dist/internals/utils/logFoundIssues.d.ts +2 -0
- package/dist/internals/utils/logFoundIssues.js +23 -0
- package/dist/internals/utils/logger.d.ts +6 -0
- package/dist/internals/utils/logger.js +15 -0
- package/dist/internals/utils/minimumTouchableSize.d.ts +1 -0
- package/dist/internals/utils/minimumTouchableSize.js +9 -0
- package/dist/internals/utils/platformHelpers.d.ts +2 -0
- package/dist/internals/utils/platformHelpers.js +6 -0
- package/dist/internals/utils/rules.d.ts +22 -0
- package/dist/internals/utils/rules.js +253 -0
- package/dist/scripts/create-config.js +4 -3
- package/package.json +62 -20
- package/src/ReactNativeAma.types.ts +70 -0
- package/src/ReactNativeAmaModule.ts +18 -0
- package/src/ReactNativeAmaModule.web.ts +14 -0
- package/src/ReactNativeAmaView.tsx +9 -0
- package/src/ReactNativeAmaView.web.tsx +15 -0
- package/src/__mocks__/ReactNativeAmaModule.ts +9 -0
- package/src/components/AMAProvider.integration.test.tsx +3 -96
- package/src/components/AMAProvider.test.tsx +9 -4
- package/src/components/AMAProvider.tsx +40 -76
- package/src/components/AutofocusContainer.test.tsx +4 -5
- package/src/components/AutofocusContainer.tsx +20 -5
- package/src/components/HideChildrenFromAccessibilityTree.test.tsx +23 -39
- package/src/components/HideChildrenFromAccessibilityTree.tsx +12 -11
- package/src/hooks/useFocus.test.ts +20 -20
- package/src/hooks/useFocus.ts +14 -6
- package/src/hooks/useTimedAction.android.test.ts +0 -1
- package/src/hooks/useTimedAction.ios.test.ts +0 -1
- package/src/hooks/useTimedAction.ts +0 -1
- package/src/index.ts +4 -2
- package/src/internals/checks/checkAriaLabel.test.ts +165 -0
- package/src/internals/checks/checkAriaLabel.ts +16 -0
- package/src/internals/checks/checkAriaRole.test.ts +429 -0
- package/src/internals/checks/checkAriaRole.ts +60 -0
- package/src/internals/checks/checkContrast.test.ts +278 -0
- package/src/internals/checks/checkContrast.ts +109 -0
- package/src/internals/checks/checkImageAlt.test.ts +125 -0
- package/src/internals/checks/checkImageAlt.ts +23 -0
- package/src/internals/checks/checkIsUppercase.test.ts +399 -0
- package/src/internals/checks/checkIsUppercase.ts +46 -0
- package/src/internals/checks/checkLongNumber.test.ts +111 -0
- package/src/internals/checks/checkLongNumber.ts +34 -0
- package/src/internals/checks/checkMinimumSize.test.ts +67 -0
- package/src/internals/checks/checkMinimumSize.ts +25 -0
- package/src/internals/checks/checkTextInput.ts +65 -0
- package/src/internals/checks/performChecks.test.ts +171 -0
- package/src/internals/checks/performChecks.ts +24 -0
- package/src/internals/components/AMAErrorOverlay.test.tsx +139 -0
- package/src/internals/components/AMAErrorOverlay.tsx +451 -0
- package/src/internals/components/AMARuleError.test.tsx +93 -0
- package/src/internals/components/AMARuleError.tsx +225 -0
- package/src/internals/config.ts +58 -0
- package/src/internals/types.ts +11 -0
- package/src/internals/useAMADev.dev.ts +153 -0
- package/src/internals/useAMADev.test.ts +260 -0
- package/src/internals/useAMADev.ts +17 -0
- package/src/internals/useAMADevNodeChecks.dev.ts +178 -0
- package/src/internals/useAMADevUiInteraction.dev.ts +164 -0
- package/src/internals/utils/amaClearHighlight.test.ts +21 -0
- package/src/internals/utils/amaClearHighlight.ts +8 -0
- package/src/internals/utils/amaHighlightComponent.test.ts +74 -0
- package/src/internals/utils/amaHighlightComponent.ts +46 -0
- package/src/internals/utils/constants.ts +33 -0
- package/src/internals/utils/devMode.test.ts +99 -0
- package/src/internals/utils/getErrorColor.test.ts +18 -0
- package/src/internals/utils/getErrorColor.ts +8 -0
- package/src/internals/utils/getRuleAction.ts +13 -0
- package/src/internals/utils/getRuleErrorInfo.test.ts +59 -0
- package/src/internals/utils/getRuleErrorInfo.ts +26 -0
- package/src/internals/utils/ignoreContrastCheck.test.ts +34 -0
- package/src/internals/utils/ignoreContrastCheck.ts +11 -0
- package/src/internals/utils/interpolateAnimation.test.ts +176 -0
- package/src/internals/utils/interpolateAnimationStates.ts +55 -0
- package/src/internals/utils/isA11yLabelAllowed.test.ts +39 -0
- package/src/internals/utils/isA11yLabelAllowed.ts +10 -0
- package/src/internals/utils/isRuleDisabled.test.ts +35 -0
- package/src/internals/utils/isRuleDisabled.ts +10 -0
- package/src/internals/utils/logError.test.ts +148 -0
- package/src/internals/utils/logError.ts +41 -0
- package/src/internals/utils/logFoundIssues.test.ts +98 -0
- package/src/internals/utils/logFoundIssues.ts +22 -0
- package/src/internals/utils/logger.test.ts +26 -0
- package/src/internals/utils/logger.ts +15 -0
- package/src/internals/utils/loggerMethods.test.ts +38 -0
- package/src/internals/utils/minimumTouchableSize.test.ts +28 -0
- package/src/internals/utils/minimumTouchableSize.tsx +9 -0
- package/src/internals/utils/platformHelpers.test.ts +11 -0
- package/src/internals/utils/platformHelpers.ts +4 -0
- package/src/internals/utils/rules.ts +363 -0
- package/src/scripts/create-config.ts +21 -0
- package/dist/src/hooks/useButtonChecks.d.ts +0 -6
- package/dist/src/hooks/useButtonChecks.js +0 -51
- package/dist/src/hooks/useChecks.d.ts +0 -15
- package/dist/src/hooks/useChecks.js +0 -152
- package/dist/src/index.js +0 -20
- package/src/hooks/useButtonChecks.ts +0 -73
- package/src/hooks/useChecks.test.ts +0 -214
- package/src/hooks/useChecks.ts +0 -207
- /package/dist/{src/components → components}/AutofocusContainer.js +0 -0
- /package/dist/{src/hooks → hooks}/useFocus.d.ts +0 -0
- /package/dist/{src/hooks → hooks}/useTimedAction.d.ts +0 -0
- /package/dist/{src/hooks → hooks}/useTimedAction.js +0 -0
- /package/dist/{src/utils → utils}/numerify.d.ts +0 -0
- /package/dist/{src/utils → utils}/numerify.js +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { AmaNode } from '../../ReactNativeAma.types';
|
|
3
|
+
import { AmaError } from '../types';
|
|
4
|
+
import { isRuleDisabled } from '../utils/isRuleDisabled';
|
|
5
|
+
|
|
6
|
+
const RETURN_DONE = Platform.select({ ios: 0, default: 6 });
|
|
7
|
+
|
|
8
|
+
const REMOVE_ENDING_REGEXT = /\W$/;
|
|
9
|
+
|
|
10
|
+
const removeEndingSymbol = (text?: string) => {
|
|
11
|
+
if (!text) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const result = text.replace(REMOVE_ENDING_REGEXT, '').trim().toLowerCase();
|
|
16
|
+
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const checkTextInputs = (nodesToCheck: AmaNode[]): AmaError[] => {
|
|
21
|
+
const inputs = nodesToCheck.filter((node) => node.type === 'TextInput');
|
|
22
|
+
const errors: AmaError[] = [];
|
|
23
|
+
|
|
24
|
+
for (const key in inputs) {
|
|
25
|
+
const index = parseInt(key, 10);
|
|
26
|
+
const inputText = inputs[key];
|
|
27
|
+
const canHaveReturnTypeDone = index === inputs.length - 1;
|
|
28
|
+
const ariaLabel = removeEndingSymbol(inputText.ariaLabel);
|
|
29
|
+
const textInputLabel = ariaLabel
|
|
30
|
+
? nodesToCheck.find(
|
|
31
|
+
// Ignores symbols like ":"
|
|
32
|
+
(node) => node.type === 'Text' && removeEndingSymbol(node.content) === ariaLabel
|
|
33
|
+
)
|
|
34
|
+
: false;
|
|
35
|
+
|
|
36
|
+
const isLabelAlsoAccessible =
|
|
37
|
+
textInputLabel && textInputLabel?.isAccessible;
|
|
38
|
+
const hasLabel = Boolean(textInputLabel);
|
|
39
|
+
|
|
40
|
+
if (inputText.returnType === RETURN_DONE && !canHaveReturnTypeDone) {
|
|
41
|
+
errors.push({
|
|
42
|
+
rule: 'INPUT_INVALID_RETURN_KEY',
|
|
43
|
+
viewId: inputText.viewId,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isLabelAlsoAccessible) {
|
|
48
|
+
errors.push({
|
|
49
|
+
rule: 'INPUT_HAS_FOCUSABLE_LABEL',
|
|
50
|
+
viewId: textInputLabel.viewId,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (ariaLabel && !hasLabel) {
|
|
55
|
+
errors.push({
|
|
56
|
+
rule: 'INPUT_HAS_NO_VISIBLE_LABEL',
|
|
57
|
+
viewId: inputText.viewId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return errors.filter(
|
|
63
|
+
(item): item is AmaError => !isRuleDisabled?.(item)
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { AmaNode } from '../../ReactNativeAma.types';
|
|
2
|
+
import { checkTextInputs } from './checkTextInput';
|
|
3
|
+
import { performChecks } from './performChecks';
|
|
4
|
+
|
|
5
|
+
jest.mock('../utils/isRuleDisabled', () => ({
|
|
6
|
+
isRuleDisabled: jest.fn(() => false),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock('../utils/getRuleAction', () => ({
|
|
10
|
+
getRuleAction: jest.fn(() => 'PLEASE_FORGIVE_ME'),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock('../utils/ignoreContrastCheck', () => ({
|
|
14
|
+
shouldIgnoreContrastCheckForDisabledElement: jest.fn(() => false),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
const { isRuleDisabled } = require('../utils/isRuleDisabled');
|
|
20
|
+
isRuleDisabled.mockReturnValue(false);
|
|
21
|
+
const { getRuleAction } = require('../utils/getRuleAction');
|
|
22
|
+
getRuleAction.mockReturnValue('PLEASE_FORGIVE_ME');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const createNode = (overrides: Partial<AmaNode> = {}): AmaNode => ({
|
|
26
|
+
type: 'Pressable',
|
|
27
|
+
viewId: 1,
|
|
28
|
+
bounds: [100, 100, 0, 0],
|
|
29
|
+
hasOnSubmitEditing: false,
|
|
30
|
+
...overrides,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('checkTextInputs', () => {
|
|
34
|
+
describe('Label is focusable', () => {
|
|
35
|
+
it('reports INPUT_HAS_FOCUSABLE_LABEL when Text node matches aria-label and is accessible', () => {
|
|
36
|
+
const nodes: AmaNode[] = [
|
|
37
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Email:', isAccessible: undefined }),
|
|
38
|
+
createNode({ type: 'Text', viewId: 2, content: 'Email', isAccessible: true }),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const result = checkTextInputs(nodes);
|
|
42
|
+
expect(result).toContainEqual({ rule: 'INPUT_HAS_FOCUSABLE_LABEL', viewId: 2 });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not report INPUT_HAS_FOCUSABLE_LABEL when Text node is not accessible', () => {
|
|
46
|
+
const nodes: AmaNode[] = [
|
|
47
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Email:', isAccessible: undefined }),
|
|
48
|
+
createNode({ type: 'Text', viewId: 2, content: 'Email', isAccessible: false }),
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const result = checkTextInputs(nodes);
|
|
52
|
+
expect(result.find(e => e.rule === 'INPUT_HAS_FOCUSABLE_LABEL')).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('does not report INPUT_HAS_FOCUSABLE_LABEL when matching Text node is missing', () => {
|
|
56
|
+
const nodes: AmaNode[] = [
|
|
57
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Email:', isAccessible: undefined }),
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const result = checkTextInputs(nodes);
|
|
61
|
+
expect(result.find(e => e.rule === 'INPUT_HAS_FOCUSABLE_LABEL')).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('strips trailing punctuation when matching label text', () => {
|
|
65
|
+
const nodes: AmaNode[] = [
|
|
66
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Name:', isAccessible: undefined }),
|
|
67
|
+
createNode({ type: 'Text', viewId: 2, content: 'Name', isAccessible: true }),
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const result = checkTextInputs(nodes);
|
|
71
|
+
expect(result).toContainEqual({ rule: 'INPUT_HAS_FOCUSABLE_LABEL', viewId: 2 });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('performs case-insensitive matching', () => {
|
|
75
|
+
const nodes: AmaNode[] = [
|
|
76
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'EMAIL:', isAccessible: undefined }),
|
|
77
|
+
createNode({ type: 'Text', viewId: 2, content: 'email', isAccessible: true }),
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const result = checkTextInputs(nodes);
|
|
81
|
+
expect(result).toContainEqual({ rule: 'INPUT_HAS_FOCUSABLE_LABEL', viewId: 2 });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Missing visible label', () => {
|
|
86
|
+
it('reports INPUT_HAS_NO_VISIBLE_LABEL when aria-label is set but no matching Text node', () => {
|
|
87
|
+
const nodes: AmaNode[] = [
|
|
88
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Email:', isAccessible: undefined }),
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const result = checkTextInputs(nodes);
|
|
92
|
+
expect(result).toContainEqual({ rule: 'INPUT_HAS_NO_VISIBLE_LABEL', viewId: 1 });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('does not report INPUT_HAS_NO_VISIBLE_LABEL when no aria-label is set', () => {
|
|
96
|
+
const nodes: AmaNode[] = [
|
|
97
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: undefined, isAccessible: undefined }),
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const result = checkTextInputs(nodes);
|
|
101
|
+
expect(result.find(e => e.rule === 'INPUT_HAS_NO_VISIBLE_LABEL')).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('does not report INPUT_HAS_NO_VISIBLE_LABEL when matching Text node exists', () => {
|
|
105
|
+
const nodes: AmaNode[] = [
|
|
106
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Email:', isAccessible: undefined }),
|
|
107
|
+
createNode({ type: 'Text', viewId: 2, content: 'Email', isAccessible: false }),
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const result = checkTextInputs(nodes);
|
|
111
|
+
expect(result.find(e => e.rule === 'INPUT_HAS_NO_VISIBLE_LABEL')).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Invalid return key', () => {
|
|
116
|
+
it('reports INPUT_INVALID_RETURN_KEY for non-last input with returnType RETURN_DONE', () => {
|
|
117
|
+
const nodes: AmaNode[] = [
|
|
118
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Email:', returnType: 0, isAccessible: undefined }),
|
|
119
|
+
createNode({ type: 'TextInput', viewId: 2, ariaLabel: 'Password:', returnType: 1, isAccessible: undefined }),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
const result = checkTextInputs(nodes);
|
|
123
|
+
expect(result).toContainEqual({ rule: 'INPUT_INVALID_RETURN_KEY', viewId: 1 });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('does not report INPUT_INVALID_RETURN_KEY for last input with returnType RETURN_DONE', () => {
|
|
127
|
+
const nodes: AmaNode[] = [
|
|
128
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Email:', returnType: 0, isAccessible: undefined }),
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const result = checkTextInputs(nodes);
|
|
132
|
+
expect(result.find(e => e.rule === 'INPUT_INVALID_RETURN_KEY')).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('Rule disable via config', () => {
|
|
137
|
+
it('filters out disabled rules from checkTextInputs results', () => {
|
|
138
|
+
const { isRuleDisabled } = require('../utils/isRuleDisabled');
|
|
139
|
+
isRuleDisabled.mockImplementation((error: { rule: string }) =>
|
|
140
|
+
error.rule === 'INPUT_HAS_NO_VISIBLE_LABEL',
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const nodes: AmaNode[] = [
|
|
144
|
+
createNode({ type: 'TextInput', viewId: 1, ariaLabel: 'Email:', isAccessible: undefined }),
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const result = checkTextInputs(nodes);
|
|
148
|
+
expect(result.find(e => e.rule === 'INPUT_HAS_NO_VISIBLE_LABEL')).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('filters out disabled rules from performChecks results', () => {
|
|
152
|
+
const { isRuleDisabled } = require('../utils/isRuleDisabled');
|
|
153
|
+
isRuleDisabled.mockImplementation((error: { rule: string }) =>
|
|
154
|
+
error.rule === 'NO_ACCESSIBILITY_LABEL',
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const node = createNode({ type: 'Pressable', ariaLabel: undefined, isAccessible: true });
|
|
158
|
+
const result = performChecks(node);
|
|
159
|
+
expect(result.find(e => e.rule === 'NO_ACCESSIBILITY_LABEL')).toBeUndefined();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('includes enabled rules in performChecks results', () => {
|
|
163
|
+
const { isRuleDisabled } = require('../utils/isRuleDisabled');
|
|
164
|
+
isRuleDisabled.mockReturnValue(false);
|
|
165
|
+
|
|
166
|
+
const node = createNode({ type: 'Pressable', ariaLabel: undefined, isAccessible: true });
|
|
167
|
+
const result = performChecks(node);
|
|
168
|
+
expect(result.find(e => e.rule === 'NO_ACCESSIBILITY_LABEL')).toBeDefined();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AmaNode } from '../../ReactNativeAma.types';
|
|
2
|
+
import { AmaError } from '../types';
|
|
3
|
+
import { isRuleDisabled } from '../utils/isRuleDisabled';
|
|
4
|
+
import { checkAriaLabel } from './checkAriaLabel';
|
|
5
|
+
import { checkAriaRole } from './checkAriaRole';
|
|
6
|
+
import { checkContrast } from './checkContrast';
|
|
7
|
+
import { checkImageAlt } from './checkImageAlt';
|
|
8
|
+
import { checkIsUppercase } from './checkIsUppercase';
|
|
9
|
+
import { checkLongNumber } from './checkLongNumber';
|
|
10
|
+
import { checkMinimumSize } from './checkMinimumSize';
|
|
11
|
+
|
|
12
|
+
export const performChecks = (node: AmaNode): AmaError[] => {
|
|
13
|
+
return [
|
|
14
|
+
checkAriaLabel(node),
|
|
15
|
+
checkAriaRole(node),
|
|
16
|
+
checkImageAlt(node),
|
|
17
|
+
checkMinimumSize(node),
|
|
18
|
+
checkIsUppercase({ node }),
|
|
19
|
+
checkContrast(node),
|
|
20
|
+
checkLongNumber(node),
|
|
21
|
+
].filter(
|
|
22
|
+
(item): item is AmaError => item !== null && !isRuleDisabled?.(item)
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { render, screen, fireEvent, act } from '@testing-library/react-native';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
const rn = jest.requireMock('react-native');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
jest.mock('../utils/amaClearHighlight', () => ({
|
|
8
|
+
amaClearHighlight: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
jest.mock('../utils/amaHighlightComponent', () => ({
|
|
11
|
+
amaHighlightComponent: jest.fn().mockResolvedValue([0, 100, 200, 50]),
|
|
12
|
+
}));
|
|
13
|
+
jest.mock('../utils/logError', () => ({
|
|
14
|
+
logError: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
jest.mock('../utils/getRuleErrorInfo', () => ({
|
|
17
|
+
getAMARuleErrorInfo: jest.fn(() => ({
|
|
18
|
+
message: 'Test message',
|
|
19
|
+
url: 'https://example.com',
|
|
20
|
+
severity: 'Critical',
|
|
21
|
+
})),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const { AMAErrorOverlay: AMAErrorComponent } = require('./AMAErrorOverlay');
|
|
25
|
+
|
|
26
|
+
const issue = { rule: 'NO_ACCESSIBILITY_LABEL' as const, viewId: 1, label: 'Button' };
|
|
27
|
+
|
|
28
|
+
describe('AMAErrorComponent', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders nothing when issues is empty', () => {
|
|
34
|
+
const { toJSON } = render(<AMAErrorComponent issues={[]} />);
|
|
35
|
+
expect(toJSON()).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders nothing when issues is undefined', () => {
|
|
39
|
+
const { toJSON } = render(<AMAErrorComponent />);
|
|
40
|
+
expect(toJSON()).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('shows the issue count in the failed bar', () => {
|
|
44
|
+
render(<AMAErrorComponent issues={[issue]} />);
|
|
45
|
+
expect(screen.getByText('Inspect ›', { includeHiddenElements: true })).toBeTruthy();
|
|
46
|
+
expect(screen.getByText(/detected/, { includeHiddenElements: true })).toBeTruthy();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders when multiple issues are provided', () => {
|
|
50
|
+
const issue2 = { rule: 'CONTRAST_FAILED' as const, viewId: 2 };
|
|
51
|
+
const { toJSON } = render(<AMAErrorComponent issues={[issue, issue2]} />);
|
|
52
|
+
expect(toJSON()).not.toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('opens the overlay when the Inspect button is pressed', async () => {
|
|
56
|
+
const { UNSAFE_getByProps } = render(<AMAErrorComponent issues={[issue]} />);
|
|
57
|
+
|
|
58
|
+
await act(async () => {
|
|
59
|
+
fireEvent.press(UNSAFE_getByProps({ role: 'button' }));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(screen.getByText('Why this matters:')).toBeTruthy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('shows issue index counter once overlay is open', async () => {
|
|
66
|
+
const issue2 = { rule: 'CONTRAST_FAILED' as const, viewId: 2 };
|
|
67
|
+
const { UNSAFE_getByProps } = render(<AMAErrorComponent issues={[issue, issue2]} />);
|
|
68
|
+
|
|
69
|
+
await act(async () => {
|
|
70
|
+
fireEvent.press(UNSAFE_getByProps({ role: 'button' }));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(screen.getByText('1 / 2')).toBeTruthy();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('closes the overlay when the close button in AMARuleError is pressed', async () => {
|
|
77
|
+
const { UNSAFE_getByProps } = render(<AMAErrorComponent issues={[issue]} />);
|
|
78
|
+
|
|
79
|
+
await act(async () => {
|
|
80
|
+
fireEvent.press(UNSAFE_getByProps({ role: 'button' }));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await act(async () => {
|
|
84
|
+
fireEvent.press(screen.getByLabelText('Close Error information'));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(screen.queryByText('Why this matters:')).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('navigates to next issue when Next is pressed', async () => {
|
|
91
|
+
const issue2 = { rule: 'CONTRAST_FAILED' as const, viewId: 2 };
|
|
92
|
+
const { UNSAFE_getByProps, UNSAFE_getAllByProps } = render(
|
|
93
|
+
<AMAErrorComponent issues={[issue, issue2]} />,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await act(async () => {
|
|
97
|
+
fireEvent.press(UNSAFE_getByProps({ role: 'button' }));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(screen.getByText('1 / 2')).toBeTruthy();
|
|
101
|
+
|
|
102
|
+
await act(async () => {
|
|
103
|
+
fireEvent.press(UNSAFE_getAllByProps({ role: 'button' }).find(
|
|
104
|
+
(el: any) => el.props['aria-label'] === undefined &&
|
|
105
|
+
el.props.importantForAccessibility === 'no'
|
|
106
|
+
) ?? UNSAFE_getAllByProps({ role: 'button' })[1]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('closes the overlay when the transparent overlay is pressed', async () => {
|
|
111
|
+
const { UNSAFE_getByProps, UNSAFE_getAllByProps } = render(<AMAErrorComponent issues={[issue]} />);
|
|
112
|
+
|
|
113
|
+
await act(async () => {
|
|
114
|
+
fireEvent.press(UNSAFE_getByProps({ role: 'button' }));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(screen.getByText('Why this matters:')).toBeTruthy();
|
|
118
|
+
|
|
119
|
+
await act(async () => {
|
|
120
|
+
const overlayPressable = UNSAFE_getAllByProps({ accessible: false }).find(
|
|
121
|
+
(el: any) => el.props.importantForAccessibility === 'no' && !el.props.accessibilityElementsHidden
|
|
122
|
+
);
|
|
123
|
+
if (overlayPressable) {
|
|
124
|
+
fireEvent.press(overlayPressable);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('renders on android (uses different bottom offset)', () => {
|
|
130
|
+
rn.Platform.OS = 'android';
|
|
131
|
+
rn.Platform.select = jest.fn((spec: any) => spec.android ?? spec.default);
|
|
132
|
+
|
|
133
|
+
const { toJSON } = render(<AMAErrorComponent issues={[issue]} />);
|
|
134
|
+
expect(toJSON()).not.toBeNull();
|
|
135
|
+
|
|
136
|
+
rn.Platform.OS = 'ios';
|
|
137
|
+
rn.Platform.select = jest.fn((spec: any) => spec.ios ?? spec.default);
|
|
138
|
+
});
|
|
139
|
+
});
|