@react-native-ama/core 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/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/package.json +61 -18
- 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/dist/scripts/create-config.d.ts +0 -1
- package/dist/scripts/create-config.js +0 -23
- 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,17 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { AmaError, AmaRule } from './types';
|
|
3
|
+
|
|
4
|
+
export type UseAMADevValue = {
|
|
5
|
+
issues: AmaError[];
|
|
6
|
+
trackError: (rule: AmaRule, ref?: RefObject<any>) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type UseAMADev = () => UseAMADevValue;
|
|
10
|
+
|
|
11
|
+
type UseAMADevModule = {
|
|
12
|
+
useAMADev: UseAMADev | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const useAMADev: UseAMADev | null = __DEV__
|
|
16
|
+
? (require('./useAMADev.dev') as UseAMADevModule).useAMADev
|
|
17
|
+
: null;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { AmaNode, AmaNodes } from '../ReactNativeAma.types';
|
|
2
|
+
import { checkTextInputs } from './checks/checkTextInput';
|
|
3
|
+
import { performChecks } from './checks/performChecks';
|
|
4
|
+
import projectRules from './config';
|
|
5
|
+
import type { AmaError } from './types';
|
|
6
|
+
import { amaClearHighlight } from './utils/amaClearHighlight';
|
|
7
|
+
import { amaHighlightComponent } from './utils/amaHighlightComponent';
|
|
8
|
+
import { logFoundIssues } from './utils/logFoundIssues';
|
|
9
|
+
|
|
10
|
+
type HighlightedIssues = Record<number, number>;
|
|
11
|
+
|
|
12
|
+
type RunNodeChecksOptions = {
|
|
13
|
+
nodesToCheck: AmaNodes;
|
|
14
|
+
previousNodeIssues: AmaError[];
|
|
15
|
+
jsFailedChecks: AmaError[];
|
|
16
|
+
highlightedIssues: HighlightedIssues;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type RunNodeChecks = (options: RunNodeChecksOptions) => AmaError[];
|
|
20
|
+
type CollectNodeIssues = (nodesToCheck: AmaNodes) => AmaError[];
|
|
21
|
+
type GetNodeIssueState = (
|
|
22
|
+
currentIssues: AmaError[],
|
|
23
|
+
nodeIssues: AmaError[],
|
|
24
|
+
nodesToCheck: AmaNodes
|
|
25
|
+
) => AmaError[];
|
|
26
|
+
type IsHeaderNode = (node: AmaNode) => boolean;
|
|
27
|
+
type ClearFixedNodeIssues = (
|
|
28
|
+
previousNodeIssues: AmaError[],
|
|
29
|
+
nodeIssues: AmaError[],
|
|
30
|
+
jsIssues: AmaError[]
|
|
31
|
+
) => void;
|
|
32
|
+
type SyncHighlightedIssues = (
|
|
33
|
+
nodeIssues: AmaError[],
|
|
34
|
+
jsFailedChecks: AmaError[],
|
|
35
|
+
highlightedIssues: HighlightedIssues
|
|
36
|
+
) => void;
|
|
37
|
+
type GroupIssuesByViewId = (issues: AmaError[]) => Record<number, AmaError[]>;
|
|
38
|
+
type SortIssuesByViewId = (issues: AmaError[]) => AmaError[];
|
|
39
|
+
type KeepNoStateHandledOrKeyboardTrapIssuesStillInView = (
|
|
40
|
+
issues: AmaError[],
|
|
41
|
+
nodesInView: Record<number, AmaNode>
|
|
42
|
+
) => AmaError[];
|
|
43
|
+
|
|
44
|
+
export const runNodeChecks: RunNodeChecks | null = __DEV__
|
|
45
|
+
? ({ nodesToCheck, previousNodeIssues, jsFailedChecks, highlightedIssues }) => {
|
|
46
|
+
const nodeIssues = collectNodeIssues?.(nodesToCheck) ?? [];
|
|
47
|
+
|
|
48
|
+
if (previousNodeIssues.length) {
|
|
49
|
+
clearFixedNodeIssues?.(previousNodeIssues, nodeIssues, jsFailedChecks);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logFoundIssues?.(nodeIssues);
|
|
53
|
+
syncHighlightedIssues?.(nodeIssues, jsFailedChecks, highlightedIssues);
|
|
54
|
+
|
|
55
|
+
return nodeIssues;
|
|
56
|
+
}
|
|
57
|
+
: null;
|
|
58
|
+
|
|
59
|
+
export const collectNodeIssues: CollectNodeIssues | null = __DEV__
|
|
60
|
+
? (nodesToCheck) => {
|
|
61
|
+
const nodes = Object.values(nodesToCheck);
|
|
62
|
+
let hasAtLeastOneHeader = false;
|
|
63
|
+
let hasTextInput = false;
|
|
64
|
+
const nodeIssues: AmaError[] = [];
|
|
65
|
+
|
|
66
|
+
for (const node of nodes) {
|
|
67
|
+
if (node.type === 'TextInput') {
|
|
68
|
+
hasTextInput = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!hasAtLeastOneHeader && isHeaderNode?.(node)) {
|
|
72
|
+
hasAtLeastOneHeader = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
nodeIssues.push(...performChecks(node));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (hasTextInput && projectRules.checks.forms) {
|
|
79
|
+
nodeIssues.push(...checkTextInputs(nodes));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!hasAtLeastOneHeader) {
|
|
83
|
+
nodeIssues.push({ rule: 'NO_HEADER_FOUND', viewId: -1 });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return nodeIssues;
|
|
87
|
+
}
|
|
88
|
+
: null;
|
|
89
|
+
|
|
90
|
+
export const getNodeIssueState: GetNodeIssueState | null = __DEV__
|
|
91
|
+
? (currentIssues, nodeIssues, nodesToCheck) => {
|
|
92
|
+
const issuesToKeep =
|
|
93
|
+
keepNoStateHandledOrKeyboardTrapIssuesStillInView?.(
|
|
94
|
+
currentIssues,
|
|
95
|
+
nodesToCheck
|
|
96
|
+
) ?? [];
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
...issuesToKeep,
|
|
100
|
+
...(sortIssuesByViewId?.(nodeIssues) ?? nodeIssues),
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
: null;
|
|
104
|
+
|
|
105
|
+
const isHeaderNode: IsHeaderNode | null = __DEV__
|
|
106
|
+
? (node) =>
|
|
107
|
+
node.type === 'Text' &&
|
|
108
|
+
(node.traits?.includes('header') || node.ariaRole === 'header')
|
|
109
|
+
: null;
|
|
110
|
+
|
|
111
|
+
const clearFixedNodeIssues: ClearFixedNodeIssues | null = __DEV__
|
|
112
|
+
? (previousNodeIssues, nodeIssues, jsIssues) => {
|
|
113
|
+
const fixedIssues = previousNodeIssues.filter(
|
|
114
|
+
(issue) =>
|
|
115
|
+
nodeIssues.find((item) => item.viewId === issue.viewId) ===
|
|
116
|
+
undefined && issue.rule !== 'NO_ACCESSIBILITY_STATE_SET'
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
for (const issue of fixedIssues) {
|
|
120
|
+
amaClearHighlight?.(issue);
|
|
121
|
+
|
|
122
|
+
const jsIndex = jsIssues.findIndex(
|
|
123
|
+
(item) => item.viewId === issue.viewId && item.rule === issue.rule
|
|
124
|
+
);
|
|
125
|
+
if (jsIndex >= 0) {
|
|
126
|
+
jsIssues.splice(jsIndex, 1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
: null;
|
|
131
|
+
|
|
132
|
+
const syncHighlightedIssues: SyncHighlightedIssues | null = __DEV__
|
|
133
|
+
? (nodeIssues, jsFailedChecks, highlightedIssues) => {
|
|
134
|
+
const issuesByViewId =
|
|
135
|
+
groupIssuesByViewId?.([...nodeIssues, ...jsFailedChecks]) ?? {};
|
|
136
|
+
|
|
137
|
+
for (const viewIdStr of Object.keys(issuesByViewId)) {
|
|
138
|
+
const viewId = Number(viewIdStr);
|
|
139
|
+
const issuesForView = issuesByViewId[viewId];
|
|
140
|
+
|
|
141
|
+
if (highlightedIssues[viewId] !== issuesForView.length) {
|
|
142
|
+
amaHighlightComponent?.(issuesForView);
|
|
143
|
+
highlightedIssues[viewId] = issuesForView.length;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
: null;
|
|
148
|
+
|
|
149
|
+
const groupIssuesByViewId: GroupIssuesByViewId | null = __DEV__
|
|
150
|
+
? (issues) => {
|
|
151
|
+
return issues.reduce((groupedIssues, issue) => {
|
|
152
|
+
if (issue.viewId >= 0) {
|
|
153
|
+
if (!groupedIssues[issue.viewId]) {
|
|
154
|
+
groupedIssues[issue.viewId] = [];
|
|
155
|
+
}
|
|
156
|
+
groupedIssues[issue.viewId].push(issue);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return groupedIssues;
|
|
160
|
+
}, {} as Record<number, AmaError[]>);
|
|
161
|
+
}
|
|
162
|
+
: null;
|
|
163
|
+
|
|
164
|
+
const sortIssuesByViewId: SortIssuesByViewId | null = __DEV__
|
|
165
|
+
? (issues) => [...issues].sort((a, b) => a.viewId - b.viewId)
|
|
166
|
+
: null;
|
|
167
|
+
|
|
168
|
+
const keepNoStateHandledOrKeyboardTrapIssuesStillInView: KeepNoStateHandledOrKeyboardTrapIssuesStillInView | null =
|
|
169
|
+
__DEV__
|
|
170
|
+
? (issues, nodesInView) => {
|
|
171
|
+
return issues.filter(
|
|
172
|
+
(item) =>
|
|
173
|
+
(item.rule === 'NO_ACCESSIBILITY_STATE_SET' ||
|
|
174
|
+
item.rule === 'NO_KEYBOARD_TRAP') &&
|
|
175
|
+
nodesInView[item.viewId]
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
: null;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AmaNode,
|
|
3
|
+
AmaUiSnapshot,
|
|
4
|
+
AmaUiSnapshotKeys,
|
|
5
|
+
AmaUiSnapshotsData,
|
|
6
|
+
} from '../ReactNativeAma.types';
|
|
7
|
+
import type { AmaError } from './types';
|
|
8
|
+
import { amaClearHighlight } from './utils/amaClearHighlight';
|
|
9
|
+
import { amaHighlightComponent } from './utils/amaHighlightComponent';
|
|
10
|
+
|
|
11
|
+
const A11Y_STATE_KEYS: AmaUiSnapshotKeys[] = [
|
|
12
|
+
'parentId',
|
|
13
|
+
'isChecked',
|
|
14
|
+
'isBusy',
|
|
15
|
+
'isSelected',
|
|
16
|
+
'isDisabled',
|
|
17
|
+
'isExpanded',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
type GetItemsWithNoStateUpdated = (
|
|
21
|
+
data: AmaUiSnapshotsData,
|
|
22
|
+
nodesInView: Record<number, AmaNode>
|
|
23
|
+
) => Set<number>;
|
|
24
|
+
|
|
25
|
+
type SyncUiInteractionIssues = (
|
|
26
|
+
prevIssues: AmaError[],
|
|
27
|
+
itemsToFlag: number[],
|
|
28
|
+
rootTag: number
|
|
29
|
+
) => AmaError[];
|
|
30
|
+
type ClearNoStateIssueForRoot = (
|
|
31
|
+
issues: AmaError[],
|
|
32
|
+
rootTag: number
|
|
33
|
+
) => AmaError[];
|
|
34
|
+
type NonNullableValue = <T>(value: T) => value is NonNullable<T>;
|
|
35
|
+
|
|
36
|
+
export const getItemsWithNoStateUpdated: GetItemsWithNoStateUpdated | null =
|
|
37
|
+
__DEV__
|
|
38
|
+
? (data, nodesInView) => {
|
|
39
|
+
const viewsBefore = data.before;
|
|
40
|
+
const viewsAfter = data.afterSettled ?? data.after;
|
|
41
|
+
const issues: Set<number> = new Set();
|
|
42
|
+
const afterKeys = Object.keys(viewsAfter).map(Number);
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
!afterKeys.includes(data.rootTag) ||
|
|
46
|
+
data.beforeModalVisible !== data.afterModalVisible
|
|
47
|
+
) {
|
|
48
|
+
return issues;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The tapped element is no longer in the UI, usually after navigation.
|
|
52
|
+
if (!nodesInView[data.rootTag]) {
|
|
53
|
+
return issues;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let hasSomethingChanged = false;
|
|
57
|
+
for (const tagId of afterKeys) {
|
|
58
|
+
const snapBefore = viewsBefore[tagId];
|
|
59
|
+
const snapAfter = viewsAfter[tagId];
|
|
60
|
+
|
|
61
|
+
if (!snapBefore) {
|
|
62
|
+
hasSomethingChanged = true;
|
|
63
|
+
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const subKeys = (
|
|
68
|
+
Object.keys(snapAfter) as Array<keyof AmaUiSnapshot>
|
|
69
|
+
).filter((key) => key !== 'parentId');
|
|
70
|
+
|
|
71
|
+
for (const subKey of subKeys) {
|
|
72
|
+
const hasPropertyChanged =
|
|
73
|
+
!A11Y_STATE_KEYS.includes(subKey) &&
|
|
74
|
+
snapBefore[subKey] !== snapAfter[subKey];
|
|
75
|
+
|
|
76
|
+
if (hasPropertyChanged) {
|
|
77
|
+
hasSomethingChanged = true;
|
|
78
|
+
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (hasSomethingChanged) {
|
|
85
|
+
const parentId = data.rootTag;
|
|
86
|
+
const after = data.after[parentId];
|
|
87
|
+
const settled = data.afterSettled?.[parentId];
|
|
88
|
+
const before = data.before[parentId];
|
|
89
|
+
|
|
90
|
+
const hasStateChanged = A11Y_STATE_KEYS.some(
|
|
91
|
+
(key) =>
|
|
92
|
+
before[key] !== after[key] ||
|
|
93
|
+
(settled && before[key] !== settled[key])
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (parentId && !hasStateChanged) {
|
|
97
|
+
issues.add(parentId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return issues;
|
|
102
|
+
}
|
|
103
|
+
: null;
|
|
104
|
+
|
|
105
|
+
export const syncUiInteractionIssues: SyncUiInteractionIssues | null = __DEV__
|
|
106
|
+
? (prevIssues, itemsToFlag, rootTag) => {
|
|
107
|
+
if (itemsToFlag.length === 0) {
|
|
108
|
+
return clearNoStateIssueForRoot?.(prevIssues, rootTag) ?? prevIssues;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const newIssues = itemsToFlag
|
|
112
|
+
.map((viewId) => {
|
|
113
|
+
const found = prevIssues.find(
|
|
114
|
+
(item) =>
|
|
115
|
+
item.viewId === viewId &&
|
|
116
|
+
item.rule === 'NO_ACCESSIBILITY_STATE_SET'
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (found) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const issue: AmaError = {
|
|
124
|
+
rule: 'NO_ACCESSIBILITY_STATE_SET',
|
|
125
|
+
viewId,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
amaHighlightComponent?.(issue);
|
|
129
|
+
|
|
130
|
+
return issue;
|
|
131
|
+
})
|
|
132
|
+
.filter(
|
|
133
|
+
(issue): issue is AmaError => nonNullable?.(issue) ?? false
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (newIssues.length === 0) {
|
|
137
|
+
return prevIssues;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return [...prevIssues, ...newIssues];
|
|
141
|
+
}
|
|
142
|
+
: null;
|
|
143
|
+
|
|
144
|
+
const clearNoStateIssueForRoot: ClearNoStateIssueForRoot | null = __DEV__
|
|
145
|
+
? (issues, rootTag) => {
|
|
146
|
+
const issueIndex = issues.findIndex(
|
|
147
|
+
(item) =>
|
|
148
|
+
item.viewId === rootTag && item.rule === 'NO_ACCESSIBILITY_STATE_SET'
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (issueIndex < 0) {
|
|
152
|
+
return issues;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
amaClearHighlight?.(issues[issueIndex]);
|
|
156
|
+
|
|
157
|
+
return issues.filter((_, index) => index !== issueIndex);
|
|
158
|
+
}
|
|
159
|
+
: null;
|
|
160
|
+
|
|
161
|
+
const nonNullable: NonNullableValue | null = __DEV__
|
|
162
|
+
? <T>(value: T): value is NonNullable<T> =>
|
|
163
|
+
value !== null && value !== undefined
|
|
164
|
+
: null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
beforeEach(() => {
|
|
2
|
+
jest.resetModules();
|
|
3
|
+
jest.clearAllMocks();
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
global.__DEV__ = true;
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe('amaClearHighlight', () => {
|
|
9
|
+
it('calls ReactNativeAmaModule.clearHighlight with the viewId', () => {
|
|
10
|
+
const clearHighlight = jest.fn();
|
|
11
|
+
jest.doMock('../../ReactNativeAmaModule', () => ({
|
|
12
|
+
__esModule: true,
|
|
13
|
+
default: { clearHighlight },
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const { amaClearHighlight } = require('./amaClearHighlight');
|
|
17
|
+
amaClearHighlight({ rule: 'NO_ACCESSIBILITY_LABEL', viewId: 42 });
|
|
18
|
+
|
|
19
|
+
expect(clearHighlight).toHaveBeenCalledWith(42);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
beforeEach(() => {
|
|
2
|
+
jest.resetModules();
|
|
3
|
+
jest.clearAllMocks();
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
global.__DEV__ = true;
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe('amaHighlightComponent', () => {
|
|
9
|
+
const setupMocks = (highlightMock = jest.fn().mockResolvedValue({ x: 0 })) => {
|
|
10
|
+
jest.doMock('../../ReactNativeAmaModule', () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
default: { highlight: highlightMock },
|
|
13
|
+
}));
|
|
14
|
+
jest.doMock('../config', () => ({
|
|
15
|
+
__esModule: true,
|
|
16
|
+
default: { highlight: { mode: 'border' }, rules: {} },
|
|
17
|
+
}));
|
|
18
|
+
jest.doMock('./rules', () => ({
|
|
19
|
+
RULES_HELP: {
|
|
20
|
+
NO_ACCESSIBILITY_LABEL: { severity: 'Critical' },
|
|
21
|
+
CONTRAST_FAILED: { severity: 'Serious' },
|
|
22
|
+
CONTRAST_FAILED_AAA: { severity: 'Warning' },
|
|
23
|
+
},
|
|
24
|
+
AMA_COLORS: { Critical: '#f00', Serious: '#2563EB', Warning: '#2563EB' },
|
|
25
|
+
}));
|
|
26
|
+
return highlightMock;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
it('returns null for an empty issues array', async () => {
|
|
30
|
+
setupMocks();
|
|
31
|
+
const { amaHighlightComponent } = require('./amaHighlightComponent');
|
|
32
|
+
const result = await amaHighlightComponent([]);
|
|
33
|
+
expect(result).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('calls highlight with the first issue viewId and highest severity color', async () => {
|
|
37
|
+
const highlightMock = setupMocks();
|
|
38
|
+
const { amaHighlightComponent } = require('./amaHighlightComponent');
|
|
39
|
+
|
|
40
|
+
await amaHighlightComponent([
|
|
41
|
+
{ rule: 'NO_ACCESSIBILITY_LABEL', viewId: 10 },
|
|
42
|
+
{ rule: 'CONTRAST_FAILED', viewId: 10 },
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
expect(highlightMock).toHaveBeenCalledWith(10, 'border', '#f00', 2);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('uses Warning color when only warning-severity issues are present', async () => {
|
|
49
|
+
const highlightMock = setupMocks();
|
|
50
|
+
const { amaHighlightComponent } = require('./amaHighlightComponent');
|
|
51
|
+
|
|
52
|
+
await amaHighlightComponent([{ rule: 'CONTRAST_FAILED_AAA', viewId: 5 }]);
|
|
53
|
+
|
|
54
|
+
expect(highlightMock).toHaveBeenCalledWith(5, 'border', '#2563EB', 1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('accepts a single issue object (non-array)', async () => {
|
|
58
|
+
const highlightMock = setupMocks();
|
|
59
|
+
const { amaHighlightComponent } = require('./amaHighlightComponent');
|
|
60
|
+
|
|
61
|
+
await amaHighlightComponent({ rule: 'CONTRAST_FAILED', viewId: 7 });
|
|
62
|
+
|
|
63
|
+
expect(highlightMock).toHaveBeenCalledWith(7, 'border', '#2563EB', 1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('uses the mode argument over the config default', async () => {
|
|
67
|
+
const highlightMock = setupMocks();
|
|
68
|
+
const { amaHighlightComponent } = require('./amaHighlightComponent');
|
|
69
|
+
|
|
70
|
+
await amaHighlightComponent([{ rule: 'CONTRAST_FAILED', viewId: 3 }], 'background');
|
|
71
|
+
|
|
72
|
+
expect(highlightMock).toHaveBeenCalledWith(3, 'background', '#2563EB', 1);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import ReactNativeAmaModule from '../../ReactNativeAmaModule';
|
|
2
|
+
import projectRules, { HighlightMode } from '../config';
|
|
3
|
+
import { AmaError } from '../types';
|
|
4
|
+
import { AMA_COLORS, RULES_HELP, A11ySeverity } from './rules';
|
|
5
|
+
|
|
6
|
+
// Severity priority: Critical > Serious > Warning
|
|
7
|
+
const SEVERITY_PRIORITY: Record<A11ySeverity, number> = {
|
|
8
|
+
Critical: 3,
|
|
9
|
+
Serious: 2,
|
|
10
|
+
Warning: 1,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const getHighestSeverityColor = (issues: AmaError[]): string => {
|
|
14
|
+
let highestPriority = 0;
|
|
15
|
+
let highestSeverity: A11ySeverity = 'Warning';
|
|
16
|
+
|
|
17
|
+
for (const issue of issues) {
|
|
18
|
+
const severity = RULES_HELP?.[issue.rule]?.severity ?? 'Critical';
|
|
19
|
+
const priority = SEVERITY_PRIORITY[severity];
|
|
20
|
+
if (priority > highestPriority) {
|
|
21
|
+
highestPriority = priority;
|
|
22
|
+
highestSeverity = severity;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return AMA_COLORS[highestSeverity];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const amaHighlightComponent = __DEV__
|
|
30
|
+
? async (issues: AmaError | AmaError[], mode?: HighlightMode) => {
|
|
31
|
+
const issuesArray = Array.isArray(issues) ? issues : [issues];
|
|
32
|
+
if (issuesArray.length === 0) {return null;}
|
|
33
|
+
|
|
34
|
+
const viewId = issuesArray[0].viewId;
|
|
35
|
+
const color = getHighestSeverityColor(issuesArray);
|
|
36
|
+
const issueCount = issuesArray.length;
|
|
37
|
+
|
|
38
|
+
return ReactNativeAmaModule.highlight(
|
|
39
|
+
viewId,
|
|
40
|
+
mode ?? projectRules.highlight?.mode ?? 'both',
|
|
41
|
+
color,
|
|
42
|
+
issueCount,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
: null;
|
|
46
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type MotionAnimationKey = Partial<keyof ViewStyle> | 'scale';
|
|
4
|
+
|
|
5
|
+
export const MOTION_ANIMATIONS: MotionAnimationKey[] = [
|
|
6
|
+
'left',
|
|
7
|
+
'bottom',
|
|
8
|
+
'top',
|
|
9
|
+
'right',
|
|
10
|
+
'transform',
|
|
11
|
+
'translateX',
|
|
12
|
+
'translateY',
|
|
13
|
+
'scaleX',
|
|
14
|
+
'scaleY',
|
|
15
|
+
'scale',
|
|
16
|
+
'width',
|
|
17
|
+
'height',
|
|
18
|
+
'padding',
|
|
19
|
+
'paddingBottom',
|
|
20
|
+
'paddingLeft',
|
|
21
|
+
'paddingRight',
|
|
22
|
+
'paddingTop',
|
|
23
|
+
'paddingVertical',
|
|
24
|
+
'paddingHorizontal',
|
|
25
|
+
'margin',
|
|
26
|
+
'marginBottom',
|
|
27
|
+
'marginLeft',
|
|
28
|
+
'marginRight',
|
|
29
|
+
'marginTop',
|
|
30
|
+
'marginHorizontal',
|
|
31
|
+
'marginVertical',
|
|
32
|
+
'rotation',
|
|
33
|
+
];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests the __DEV__ = false branch for modules that export null when not in dev mode.
|
|
3
|
+
* Each test uses jest.resetModules + __DEV__ = false to hit the null branch.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
describe('dev mode null exports', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.resetModules();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
global.__DEV__ = true;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('logError is null when __DEV__ is false', () => {
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
global.__DEV__ = false;
|
|
19
|
+
const { logError } = require('./logError');
|
|
20
|
+
expect(logError).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('isRuleDisabled is null when __DEV__ is false', () => {
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
global.__DEV__ = false;
|
|
26
|
+
const { isRuleDisabled } = require('./isRuleDisabled');
|
|
27
|
+
expect(isRuleDisabled).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('getRuleAction is null when __DEV__ is false', () => {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
global.__DEV__ = false;
|
|
33
|
+
const { getRuleAction } = require('./getRuleAction');
|
|
34
|
+
expect(getRuleAction).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('getAMARuleErrorInfo is null when __DEV__ is false', () => {
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
global.__DEV__ = false;
|
|
40
|
+
const { getAMARuleErrorInfo } = require('./getRuleErrorInfo');
|
|
41
|
+
expect(getAMARuleErrorInfo).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('shouldIgnoreContrastCheckForDisabledElement is null when __DEV__ is false', () => {
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
global.__DEV__ = false;
|
|
47
|
+
const { shouldIgnoreContrastCheckForDisabledElement } = require('./ignoreContrastCheck');
|
|
48
|
+
expect(shouldIgnoreContrastCheckForDisabledElement).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('isAccessibilityLabelAllowed is null when __DEV__ is false', () => {
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
global.__DEV__ = false;
|
|
54
|
+
const { isAccessibilityLabelAllowed } = require('./isA11yLabelAllowed');
|
|
55
|
+
expect(isAccessibilityLabelAllowed).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('amaClearHighlight is null when __DEV__ is false', () => {
|
|
59
|
+
// @ts-ignore
|
|
60
|
+
global.__DEV__ = false;
|
|
61
|
+
const { amaClearHighlight } = require('./amaClearHighlight');
|
|
62
|
+
expect(amaClearHighlight).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('amaHighlightComponent is null when __DEV__ is false', () => {
|
|
66
|
+
// @ts-ignore
|
|
67
|
+
global.__DEV__ = false;
|
|
68
|
+
const { amaHighlightComponent } = require('./amaHighlightComponent');
|
|
69
|
+
expect(amaHighlightComponent).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('logger is null when __DEV__ is false', () => {
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
global.__DEV__ = false;
|
|
75
|
+
const logger = require('./logger').default;
|
|
76
|
+
expect(logger).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('LOGGER_RULES is null when __DEV__ is false', () => {
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
global.__DEV__ = false;
|
|
82
|
+
const { LOGGER_RULES } = require('./rules');
|
|
83
|
+
expect(LOGGER_RULES).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('RULES_HELP is null when __DEV__ is false', () => {
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
global.__DEV__ = false;
|
|
89
|
+
const { RULES_HELP } = require('./rules');
|
|
90
|
+
expect(RULES_HELP).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('useAMADev is null when __DEV__ is false', () => {
|
|
94
|
+
// @ts-ignore
|
|
95
|
+
global.__DEV__ = false;
|
|
96
|
+
const { useAMADev } = require('../useAMADev');
|
|
97
|
+
expect(useAMADev).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getErrorColor } from './getErrorColor';
|
|
2
|
+
|
|
3
|
+
describe('getErrorColor', () => {
|
|
4
|
+
it('returns the Critical color for a Critical severity rule', () => {
|
|
5
|
+
const color = getErrorColor('NO_ACCESSIBILITY_LABEL');
|
|
6
|
+
expect(color).toBe('#f00');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('returns the Serious color for a Serious severity rule', () => {
|
|
10
|
+
const color = getErrorColor('CONTRAST_FAILED');
|
|
11
|
+
expect(color).toBe('#2563EB');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns the Critical color (fallback) for an unknown rule', () => {
|
|
15
|
+
const color = getErrorColor('UNKNOWN_RULE' as any);
|
|
16
|
+
expect(color).toBe('#f00');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import projectRules from '../config';
|
|
2
|
+
import { AmaRule, AmaRuleAction } from '../types';
|
|
3
|
+
import canRuleBeOverridden, { LOGGER_RULES } from './rules';
|
|
4
|
+
|
|
5
|
+
export const getRuleAction = __DEV__
|
|
6
|
+
? (rule: AmaRule): AmaRuleAction => {
|
|
7
|
+
const customRule = canRuleBeOverridden?.(rule)
|
|
8
|
+
? projectRules?.rules?.[rule]
|
|
9
|
+
: undefined;
|
|
10
|
+
|
|
11
|
+
return customRule || LOGGER_RULES![rule];
|
|
12
|
+
}
|
|
13
|
+
: null;
|