@react-native-ama/core 1.2.1 → 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.
Files changed (179) hide show
  1. package/dist/ReactNativeAma.types.d.ts +66 -0
  2. package/dist/ReactNativeAma.types.js +2 -0
  3. package/dist/ReactNativeAmaModule.d.ts +11 -0
  4. package/dist/ReactNativeAmaModule.js +5 -0
  5. package/dist/ReactNativeAmaModule.web.d.ts +9 -0
  6. package/dist/ReactNativeAmaModule.web.js +27 -0
  7. package/dist/ReactNativeAmaView.d.ts +3 -0
  8. package/dist/ReactNativeAmaView.js +32 -0
  9. package/dist/ReactNativeAmaView.web.d.ts +3 -0
  10. package/dist/ReactNativeAmaView.web.js +34 -0
  11. package/dist/{src/components → components}/AMAProvider.d.ts +4 -2
  12. package/dist/{src/components → components}/AMAProvider.js +19 -45
  13. package/dist/{src/components → components}/AutofocusContainer.d.ts +2 -1
  14. package/dist/{src/components → components}/HideChildrenFromAccessibilityTree.d.ts +1 -1
  15. package/dist/{src/components → components}/HideChildrenFromAccessibilityTree.js +7 -5
  16. package/dist/{src/hooks → hooks}/useFocus.js +10 -3
  17. package/dist/{src/index.d.ts → index.d.ts} +3 -2
  18. package/dist/index.js +38 -0
  19. package/dist/internals/checks/checkAriaLabel.d.ts +3 -0
  20. package/dist/internals/checks/checkAriaLabel.js +15 -0
  21. package/dist/internals/checks/checkAriaRole.d.ts +3 -0
  22. package/dist/internals/checks/checkAriaRole.js +48 -0
  23. package/dist/internals/checks/checkContrast.d.ts +3 -0
  24. package/dist/internals/checks/checkContrast.js +84 -0
  25. package/dist/internals/checks/checkImageAlt.d.ts +3 -0
  26. package/dist/internals/checks/checkImageAlt.js +20 -0
  27. package/dist/internals/checks/checkIsUppercase.d.ts +8 -0
  28. package/dist/internals/checks/checkIsUppercase.js +30 -0
  29. package/dist/internals/checks/checkLongNumber.d.ts +3 -0
  30. package/dist/internals/checks/checkLongNumber.js +31 -0
  31. package/dist/internals/checks/checkMinimumSize.d.ts +3 -0
  32. package/dist/internals/checks/checkMinimumSize.js +22 -0
  33. package/dist/internals/checks/checkTextInput.d.ts +3 -0
  34. package/dist/internals/checks/checkTextInput.js +51 -0
  35. package/dist/internals/checks/performChecks.d.ts +3 -0
  36. package/dist/internals/checks/performChecks.js +23 -0
  37. package/dist/internals/components/AMAErrorOverlay.d.ts +5 -0
  38. package/dist/internals/components/AMAErrorOverlay.js +334 -0
  39. package/dist/internals/components/AMARuleError.d.ts +7 -0
  40. package/dist/internals/components/AMARuleError.js +161 -0
  41. package/dist/internals/config.d.ts +23 -0
  42. package/dist/internals/config.js +33 -0
  43. package/dist/internals/types.d.ts +9 -0
  44. package/dist/internals/types.js +2 -0
  45. package/dist/internals/useAMADev.d.ts +8 -0
  46. package/dist/internals/useAMADev.dev.d.ts +2 -0
  47. package/dist/internals/useAMADev.dev.js +110 -0
  48. package/dist/internals/useAMADev.js +6 -0
  49. package/dist/internals/useAMADevNodeChecks.dev.d.ts +16 -0
  50. package/dist/internals/useAMADevNodeChecks.dev.js +115 -0
  51. package/dist/internals/useAMADevUiInteraction.dev.d.ts +7 -0
  52. package/dist/internals/useAMADevUiInteraction.dev.js +100 -0
  53. package/dist/internals/utils/amaClearHighlight.d.ts +2 -0
  54. package/dist/internals/utils/amaClearHighlight.js +12 -0
  55. package/dist/internals/utils/amaHighlightComponent.d.ts +3 -0
  56. package/dist/internals/utils/amaHighlightComponent.js +51 -0
  57. package/dist/internals/utils/constants.d.ts +3 -0
  58. package/dist/internals/utils/constants.js +32 -0
  59. package/dist/internals/utils/getErrorColor.d.ts +2 -0
  60. package/dist/internals/utils/getErrorColor.js +10 -0
  61. package/dist/internals/utils/getRuleAction.d.ts +2 -0
  62. package/dist/internals/utils/getRuleAction.js +40 -0
  63. package/dist/internals/utils/getRuleErrorInfo.d.ts +7 -0
  64. package/dist/internals/utils/getRuleErrorInfo.js +19 -0
  65. package/dist/internals/utils/ignoreContrastCheck.d.ts +1 -0
  66. package/dist/internals/utils/ignoreContrastCheck.js +15 -0
  67. package/dist/internals/utils/interpolateAnimationStates.d.ts +2 -0
  68. package/dist/internals/utils/interpolateAnimationStates.js +43 -0
  69. package/dist/internals/utils/isA11yLabelAllowed.d.ts +1 -0
  70. package/dist/internals/utils/isA11yLabelAllowed.js +14 -0
  71. package/dist/internals/utils/isRuleDisabled.d.ts +2 -0
  72. package/dist/internals/utils/isRuleDisabled.js +10 -0
  73. package/dist/internals/utils/logError.d.ts +2 -0
  74. package/dist/internals/utils/logError.js +36 -0
  75. package/dist/internals/utils/logFoundIssues.d.ts +2 -0
  76. package/dist/internals/utils/logFoundIssues.js +23 -0
  77. package/dist/internals/utils/logger.d.ts +6 -0
  78. package/dist/internals/utils/logger.js +15 -0
  79. package/dist/internals/utils/minimumTouchableSize.d.ts +1 -0
  80. package/dist/internals/utils/minimumTouchableSize.js +9 -0
  81. package/dist/internals/utils/platformHelpers.d.ts +2 -0
  82. package/dist/internals/utils/platformHelpers.js +6 -0
  83. package/dist/internals/utils/rules.d.ts +22 -0
  84. package/dist/internals/utils/rules.js +253 -0
  85. package/package.json +61 -18
  86. package/src/ReactNativeAma.types.ts +70 -0
  87. package/src/ReactNativeAmaModule.ts +18 -0
  88. package/src/ReactNativeAmaModule.web.ts +14 -0
  89. package/src/ReactNativeAmaView.tsx +9 -0
  90. package/src/ReactNativeAmaView.web.tsx +15 -0
  91. package/src/__mocks__/ReactNativeAmaModule.ts +9 -0
  92. package/src/components/AMAProvider.integration.test.tsx +3 -96
  93. package/src/components/AMAProvider.test.tsx +9 -4
  94. package/src/components/AMAProvider.tsx +40 -76
  95. package/src/components/AutofocusContainer.test.tsx +4 -5
  96. package/src/components/AutofocusContainer.tsx +20 -5
  97. package/src/components/HideChildrenFromAccessibilityTree.test.tsx +23 -39
  98. package/src/components/HideChildrenFromAccessibilityTree.tsx +12 -11
  99. package/src/hooks/useFocus.test.ts +20 -20
  100. package/src/hooks/useFocus.ts +14 -6
  101. package/src/hooks/useTimedAction.android.test.ts +0 -1
  102. package/src/hooks/useTimedAction.ios.test.ts +0 -1
  103. package/src/hooks/useTimedAction.ts +0 -1
  104. package/src/index.ts +4 -2
  105. package/src/internals/checks/checkAriaLabel.test.ts +165 -0
  106. package/src/internals/checks/checkAriaLabel.ts +16 -0
  107. package/src/internals/checks/checkAriaRole.test.ts +429 -0
  108. package/src/internals/checks/checkAriaRole.ts +60 -0
  109. package/src/internals/checks/checkContrast.test.ts +278 -0
  110. package/src/internals/checks/checkContrast.ts +109 -0
  111. package/src/internals/checks/checkImageAlt.test.ts +125 -0
  112. package/src/internals/checks/checkImageAlt.ts +23 -0
  113. package/src/internals/checks/checkIsUppercase.test.ts +399 -0
  114. package/src/internals/checks/checkIsUppercase.ts +46 -0
  115. package/src/internals/checks/checkLongNumber.test.ts +111 -0
  116. package/src/internals/checks/checkLongNumber.ts +34 -0
  117. package/src/internals/checks/checkMinimumSize.test.ts +67 -0
  118. package/src/internals/checks/checkMinimumSize.ts +25 -0
  119. package/src/internals/checks/checkTextInput.ts +65 -0
  120. package/src/internals/checks/performChecks.test.ts +171 -0
  121. package/src/internals/checks/performChecks.ts +24 -0
  122. package/src/internals/components/AMAErrorOverlay.test.tsx +139 -0
  123. package/src/internals/components/AMAErrorOverlay.tsx +451 -0
  124. package/src/internals/components/AMARuleError.test.tsx +93 -0
  125. package/src/internals/components/AMARuleError.tsx +225 -0
  126. package/src/internals/config.ts +58 -0
  127. package/src/internals/types.ts +11 -0
  128. package/src/internals/useAMADev.dev.ts +153 -0
  129. package/src/internals/useAMADev.test.ts +260 -0
  130. package/src/internals/useAMADev.ts +17 -0
  131. package/src/internals/useAMADevNodeChecks.dev.ts +178 -0
  132. package/src/internals/useAMADevUiInteraction.dev.ts +164 -0
  133. package/src/internals/utils/amaClearHighlight.test.ts +21 -0
  134. package/src/internals/utils/amaClearHighlight.ts +8 -0
  135. package/src/internals/utils/amaHighlightComponent.test.ts +74 -0
  136. package/src/internals/utils/amaHighlightComponent.ts +46 -0
  137. package/src/internals/utils/constants.ts +33 -0
  138. package/src/internals/utils/devMode.test.ts +99 -0
  139. package/src/internals/utils/getErrorColor.test.ts +18 -0
  140. package/src/internals/utils/getErrorColor.ts +8 -0
  141. package/src/internals/utils/getRuleAction.ts +13 -0
  142. package/src/internals/utils/getRuleErrorInfo.test.ts +59 -0
  143. package/src/internals/utils/getRuleErrorInfo.ts +26 -0
  144. package/src/internals/utils/ignoreContrastCheck.test.ts +34 -0
  145. package/src/internals/utils/ignoreContrastCheck.ts +11 -0
  146. package/src/internals/utils/interpolateAnimation.test.ts +176 -0
  147. package/src/internals/utils/interpolateAnimationStates.ts +55 -0
  148. package/src/internals/utils/isA11yLabelAllowed.test.ts +39 -0
  149. package/src/internals/utils/isA11yLabelAllowed.ts +10 -0
  150. package/src/internals/utils/isRuleDisabled.test.ts +35 -0
  151. package/src/internals/utils/isRuleDisabled.ts +10 -0
  152. package/src/internals/utils/logError.test.ts +148 -0
  153. package/src/internals/utils/logError.ts +41 -0
  154. package/src/internals/utils/logFoundIssues.test.ts +98 -0
  155. package/src/internals/utils/logFoundIssues.ts +22 -0
  156. package/src/internals/utils/logger.test.ts +26 -0
  157. package/src/internals/utils/logger.ts +15 -0
  158. package/src/internals/utils/loggerMethods.test.ts +38 -0
  159. package/src/internals/utils/minimumTouchableSize.test.ts +28 -0
  160. package/src/internals/utils/minimumTouchableSize.tsx +9 -0
  161. package/src/internals/utils/platformHelpers.test.ts +11 -0
  162. package/src/internals/utils/platformHelpers.ts +4 -0
  163. package/src/internals/utils/rules.ts +363 -0
  164. package/dist/scripts/create-config.d.ts +0 -1
  165. package/dist/scripts/create-config.js +0 -23
  166. package/dist/src/hooks/useButtonChecks.d.ts +0 -6
  167. package/dist/src/hooks/useButtonChecks.js +0 -51
  168. package/dist/src/hooks/useChecks.d.ts +0 -15
  169. package/dist/src/hooks/useChecks.js +0 -152
  170. package/dist/src/index.js +0 -20
  171. package/src/hooks/useButtonChecks.ts +0 -73
  172. package/src/hooks/useChecks.test.ts +0 -214
  173. package/src/hooks/useChecks.ts +0 -207
  174. /package/dist/{src/components → components}/AutofocusContainer.js +0 -0
  175. /package/dist/{src/hooks → hooks}/useFocus.d.ts +0 -0
  176. /package/dist/{src/hooks → hooks}/useTimedAction.d.ts +0 -0
  177. /package/dist/{src/hooks → hooks}/useTimedAction.js +0 -0
  178. /package/dist/{src/utils → utils}/numerify.d.ts +0 -0
  179. /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,8 @@
1
+ import ReactNativeAmaModule from '../../ReactNativeAmaModule';
2
+ import { AmaError } from '../types';
3
+
4
+ export const amaClearHighlight = __DEV__
5
+ ? (issue: AmaError) => {
6
+ ReactNativeAmaModule.clearHighlight(issue.viewId);
7
+ }
8
+ : null;
@@ -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,8 @@
1
+ import { AmaRule } from '../types';
2
+ import { AMA_COLORS, RULES_HELP } from './rules';
3
+
4
+ export const getErrorColor = (rule: AmaRule) => {
5
+ const theRule = RULES_HELP?.[rule];
6
+
7
+ return AMA_COLORS[theRule?.severity ?? 'Critical'];
8
+ };
@@ -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;