@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,451 @@
1
+ /* eslint-disable react-native/no-inline-styles */
2
+
3
+ import React, { PropsWithChildren, useEffect, useRef, useState } from 'react';
4
+ import {
5
+ Dimensions,
6
+ Platform,
7
+ Pressable,
8
+ Share,
9
+ StyleSheet,
10
+ Text,
11
+ View,
12
+ } from 'react-native';
13
+ import { AmaError, Position } from '../types';
14
+ import { amaClearHighlight } from '../utils/amaClearHighlight';
15
+ import { amaHighlightComponent } from '../utils/amaHighlightComponent';
16
+ import { getAMARuleErrorInfo } from '../utils/getRuleErrorInfo';
17
+ import { logError } from '../utils/logError';
18
+ import { AMA_COLORS } from '../utils/rules';
19
+ import { AMARuleError } from './AMARuleError';
20
+
21
+ const WINDOW_WIDTH = Dimensions.get('window').width;
22
+ const WINDOW_HEIGHT = Dimensions.get('screen').height;
23
+
24
+ const AMAErrorComponent = ({ issues }: { issues?: AmaError[] }) => {
25
+ const [activeIssueIndex, setActiveIssueIndex] = useState<{
26
+ id: number;
27
+ position?: Position;
28
+ }>();
29
+ const issueToView = useRef<AmaError>(null);
30
+ const previousViewId = useRef<AmaError>(null);
31
+
32
+ const clearPreviousHighlight = () => {
33
+ if (!previousViewId.current) {
34
+ return;
35
+ }
36
+
37
+ amaClearHighlight?.(previousViewId.current);
38
+
39
+ // Get all issues for the previous viewId to restore correct count and color
40
+ const issuesForPreviousView =
41
+ issues?.filter(
42
+ (issue) => issue.viewId === previousViewId.current?.viewId
43
+ ) ?? [];
44
+
45
+ if (issuesForPreviousView.length > 0) {
46
+ amaHighlightComponent?.(issuesForPreviousView);
47
+ }
48
+ };
49
+
50
+ const setActiveIssue = async (newIndex: number) => {
51
+ clearPreviousHighlight();
52
+ issueToView.current = issues![newIndex];
53
+
54
+ if (issueToView.current) {
55
+ // Get all issues for the current viewId
56
+ const issuesForView =
57
+ issues?.filter(
58
+ (issue) => issue.viewId === issueToView.current?.viewId
59
+ ) ?? [];
60
+
61
+ const position = await amaHighlightComponent?.(issuesForView, 'both');
62
+
63
+ setActiveIssueIndex({ id: newIndex, position: position ?? undefined });
64
+
65
+ previousViewId.current = issueToView.current;
66
+ } else {
67
+ setActiveIssueIndex(undefined);
68
+ previousViewId.current = null;
69
+ }
70
+ };
71
+
72
+ const showNextIssue = () => {
73
+ setActiveIssue(activeIssueIndex?.id != null ? activeIssueIndex.id + 1 : 0);
74
+ };
75
+
76
+ const showPrevIssue = () => {
77
+ setActiveIssue(
78
+ activeIssueIndex?.id && activeIssueIndex?.id > 0
79
+ ? activeIssueIndex?.id - 1
80
+ : 0
81
+ );
82
+ };
83
+
84
+ const closeIssues = () => {
85
+ clearPreviousHighlight();
86
+ setActiveIssueIndex(undefined);
87
+ };
88
+
89
+ const showFirstError = () => {
90
+ showNextIssue();
91
+ };
92
+
93
+ if (!issues?.length) {
94
+ return null;
95
+ }
96
+
97
+ const showActiveIssue = activeIssueIndex !== undefined && issueToView.current;
98
+ const failedBarBottom = getFailedBarBottomOffset();
99
+
100
+ return (
101
+ <>
102
+ {showActiveIssue ? (
103
+ <AMAOverlay
104
+ issue={issueToView.current!}
105
+ position={activeIssueIndex.position}
106
+ closeOverlay={closeIssues}
107
+ onPrevIssue={showPrevIssue}
108
+ onNextIssue={showNextIssue}
109
+ onClose={closeIssues}
110
+ totalIssues={issues.length}
111
+ issueIndex={activeIssueIndex.id}
112
+ />
113
+ ) : null}
114
+ <View
115
+ style={[styles!.failedBar, { bottom: failedBarBottom }]}
116
+ accessible={false}
117
+ accessibilityElementsHidden
118
+ importantForAccessibility="no-hide-descendants"
119
+ >
120
+ <AMAButton onPress={showFirstError} bg="transparent" color="#fff">
121
+ <Text style={styles!.buttonText}>
122
+ <Text style={{ color: 'yellow' }}>
123
+ ⚠️ AMA: {issues.length} issue{issues.length !== 1 ? 's' : ''}{' '}
124
+ detected
125
+ </Text>
126
+ <Text style={{ color: '#fafafa' }}>{' ┆ '}</Text>
127
+ <Text style={{ fontWeight: 'bold' }}>Inspect ›</Text>
128
+ </Text>
129
+ </AMAButton>
130
+ </View>
131
+ </>
132
+ );
133
+ };
134
+
135
+ type AMAButtonProps = PropsWithChildren<{
136
+ count?: number;
137
+ singular?: string;
138
+ color: string;
139
+ bg: string;
140
+ line?: string;
141
+ onPress: () => void;
142
+ disabled?: boolean;
143
+ }>;
144
+
145
+ const AMAButton = ({
146
+ children,
147
+ count,
148
+ singular,
149
+ bg,
150
+ onPress,
151
+ line,
152
+ disabled,
153
+ }: AMAButtonProps) => {
154
+ const plural = count ? `${singular}${count !== 1 ? 's' : ''} ` : singular;
155
+
156
+ return (
157
+ <Pressable
158
+ onPress={disabled ? undefined : onPress}
159
+ style={({ pressed }) => {
160
+ const opacity = pressed || disabled ? 0.8 : 1;
161
+ const divider = line
162
+ ? {
163
+ borderRightWidth: 1,
164
+ borderRightColor: line,
165
+ }
166
+ : {};
167
+
168
+ return {
169
+ ...styles!.button,
170
+ ...divider,
171
+ backgroundColor: bg,
172
+ opacity,
173
+ };
174
+ }}
175
+ role="button"
176
+ aria-label={`Show ${plural}`}
177
+ aria-disabled={disabled}
178
+ importantForAccessibility="no"
179
+ accessibilityElementsHidden
180
+ >
181
+ {children}
182
+ </Pressable>
183
+ );
184
+ };
185
+
186
+ type AMAOverlayProps = {
187
+ issue: AmaError;
188
+ position?: Position;
189
+ closeOverlay: () => void;
190
+ onPrevIssue: () => void;
191
+ onNextIssue: () => void;
192
+ onClose: () => void;
193
+ totalIssues: number;
194
+ issueIndex: number;
195
+ };
196
+
197
+ const AMAOverlay = ({
198
+ issue,
199
+ position,
200
+ closeOverlay,
201
+ onNextIssue,
202
+ onPrevIssue,
203
+ onClose,
204
+ totalIssues,
205
+ issueIndex,
206
+ }: AMAOverlayProps) => {
207
+ position = position || [24, 100, WINDOW_WIDTH - 48, 0];
208
+ const { severity, url } = getAMARuleErrorInfo!(issue);
209
+ const [x, y, width, height] = position;
210
+ const isMoreThanHalfScreen = y / WINDOW_HEIGHT > 0.57;
211
+ const maxHeight = isMoreThanHalfScreen
212
+ ? WINDOW_HEIGHT * 0.43
213
+ : WINDOW_HEIGHT - y - 160;
214
+ const yPosition = isMoreThanHalfScreen
215
+ ? undefined
216
+ : y + height + POINTER_SIZE + SPACER;
217
+ const bottomPosition = isMoreThanHalfScreen
218
+ ? WINDOW_HEIGHT - y + SPACER + POINTER_SIZE
219
+ : undefined;
220
+
221
+ const openHelp = async () => {
222
+ try {
223
+ await Share.share({
224
+ message: url,
225
+ });
226
+ } catch (error) {
227
+ console.error(error);
228
+ }
229
+ };
230
+
231
+ useEffect(() => {
232
+ logError?.(issue);
233
+ }, [issue]);
234
+
235
+ return (
236
+ <>
237
+ <Pressable
238
+ style={styles!.transparentOverlay}
239
+ onPress={closeOverlay}
240
+ accessible={false}
241
+ importantForAccessibility="no"
242
+ />
243
+ <View
244
+ style={[
245
+ styles?.callout,
246
+ {
247
+ top: yPosition,
248
+ bottom: bottomPosition,
249
+ borderColor: AMA_COLORS[severity],
250
+ },
251
+ ]}
252
+ >
253
+ <Text style={styles!.totalIssues}>
254
+ {issueIndex + 1} / {totalIssues}
255
+ </Text>
256
+ {AMARuleError ? (
257
+ <AMARuleError issue={issue} onClose={onClose} maxHeight={maxHeight} />
258
+ ) : null}
259
+
260
+ <View style={styles!.actions}>
261
+ <Pressable
262
+ onPress={onPrevIssue}
263
+ role="button"
264
+ hitSlop={{ left: 12, top: 12, bottom: 12, right: 12 }}
265
+ style={[styles!.action, styles!.previous]}
266
+ accessible={false}
267
+ importantForAccessibility="no"
268
+ >
269
+ <Text>‹ Prev</Text>
270
+ </Pressable>
271
+ <Pressable
272
+ onPress={openHelp}
273
+ role="button"
274
+ hitSlop={{ left: 12, top: 12, bottom: 12, right: 12 }}
275
+ style={[styles!.action, styles!.learnMore]}
276
+ accessible={false}
277
+ importantForAccessibility="no"
278
+ >
279
+ <Text style={styles!.link}>Learn more</Text>
280
+ </Pressable>
281
+
282
+ <Pressable
283
+ onPress={onNextIssue}
284
+ role="button"
285
+ hitSlop={{ left: 12, top: 12, bottom: 12, right: 12 }}
286
+ style={[styles!.action, styles!.next]}
287
+ accessible={false}
288
+ importantForAccessibility="no"
289
+ >
290
+ <Text style={{ textAlign: 'right' }}>Next ›</Text>
291
+ </Pressable>
292
+ </View>
293
+ </View>
294
+ <View
295
+ style={[
296
+ styles!.triangle,
297
+ {
298
+ left: x + width / 2 - POINTER_SIZE,
299
+ },
300
+ isMoreThanHalfScreen
301
+ ? {
302
+ top: y - POINTER_SIZE - SPACER,
303
+ borderTopWidth: POINTER_SIZE,
304
+ borderTopColor: AMA_COLORS[severity],
305
+ borderBottomWidth: 0,
306
+ }
307
+ : {
308
+ top: y + height + SPACER,
309
+ borderBottomColor: AMA_COLORS[severity],
310
+ },
311
+ ]}
312
+ />
313
+ </>
314
+ );
315
+ };
316
+
317
+ export const AMAErrorOverlay = __DEV__ ? AMAErrorComponent : null;
318
+
319
+ const SPACER = 4;
320
+ const POINTER_SIZE = 8;
321
+ const Z_INDEX = 9999;
322
+
323
+ const getFailedBarBottomOffset = () => {
324
+ if (Platform.OS !== 'android') {
325
+ return 40;
326
+ }
327
+
328
+ return 60;
329
+ };
330
+
331
+ const styles = __DEV__
332
+ ? StyleSheet.create({
333
+ callout: {
334
+ position: 'absolute',
335
+ left: 24,
336
+ right: 24,
337
+ backgroundColor: '#fff',
338
+ padding: 0,
339
+ justifyContent: 'center',
340
+ alignItems: 'center',
341
+ zIndex: Z_INDEX + 1,
342
+ shadowOffset: {
343
+ width: 10,
344
+ height: -10,
345
+ },
346
+ boxShadow: '0px 2px 20px #000',
347
+ shadowOpacity: 0.2,
348
+ shadowRadius: 24,
349
+ elevation: 4,
350
+ borderTopWidth: 2,
351
+ overflow: 'hidden',
352
+ },
353
+ calloutText: {
354
+ color: '#000',
355
+ fontSize: 14,
356
+ marginBottom: 12,
357
+ },
358
+ failedBar: {
359
+ backgroundColor: '#A31420',
360
+ alignSelf: 'center',
361
+ flexDirection: 'row',
362
+ alignItems: 'center',
363
+ shadowOffset: {
364
+ width: 10,
365
+ height: 10,
366
+ },
367
+ boxShadow: '0px 2px 6px #000',
368
+ shadowOpacity: 0.05,
369
+ shadowRadius: 12,
370
+ zIndex: Z_INDEX,
371
+ borderRadius: 20,
372
+ maxWidth: '90%',
373
+ },
374
+ button: {
375
+ flexDirection: 'row',
376
+ alignItems: 'center',
377
+ flex: 1,
378
+ padding: 12,
379
+ },
380
+ buttonText: {
381
+ flex: 1,
382
+ fontSize: 16,
383
+ lineHeight: 24,
384
+ textAlign: 'center',
385
+ color: '#fff',
386
+ },
387
+ transparentOverlay: {
388
+ position: 'absolute',
389
+ left: 0,
390
+ top: 0,
391
+ right: 0,
392
+ bottom: 0,
393
+ zIndex: Z_INDEX - 1,
394
+ backgroundColor: 'transparent',
395
+ },
396
+ actions: {
397
+ flexDirection: 'row',
398
+ justifyContent: 'space-between',
399
+ flex: 1,
400
+ alignItems: 'center',
401
+ alignContent: 'center',
402
+ },
403
+ action: {
404
+ paddingVertical: 8,
405
+ marginBottom: 12,
406
+ width: '27%',
407
+ borderWidth: 1,
408
+ },
409
+ previous: {
410
+ borderLeftWidth: 0,
411
+ borderTopEndRadius: 18,
412
+ borderBottomEndRadius: 18,
413
+ paddingRight: 12,
414
+ paddingLeft: 12,
415
+ },
416
+ next: {
417
+ borderRightWidth: 0,
418
+ borderTopLeftRadius: 18,
419
+ borderBottomLeftRadius: 18,
420
+ paddingRight: 12,
421
+ paddingLeft: 12,
422
+ },
423
+ learnMore: {
424
+ flex: 1,
425
+ alignItems: 'center',
426
+ borderRadius: 24,
427
+ borderWidth: 1,
428
+ marginHorizontal: 24,
429
+ paddingVertical: 8,
430
+ backgroundColor: '#07549C',
431
+ borderColor: '#07549C',
432
+ },
433
+ link: {
434
+ color: '#fff',
435
+ },
436
+ totalIssues: {
437
+ marginTop: 8,
438
+ },
439
+ triangle: {
440
+ position: 'absolute',
441
+ width: 0,
442
+ height: 0,
443
+ borderLeftWidth: POINTER_SIZE,
444
+ borderRightWidth: POINTER_SIZE,
445
+ borderBottomWidth: POINTER_SIZE,
446
+ borderLeftColor: 'transparent',
447
+ borderRightColor: 'transparent',
448
+ zIndex: Z_INDEX + 10,
449
+ },
450
+ })
451
+ : null;
@@ -0,0 +1,93 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react-native';
2
+ import React from 'react';
3
+ import { AMARuleError as AMARuleErrorImpl } from './AMARuleError';
4
+
5
+ const AMARuleError = AMARuleErrorImpl!;
6
+
7
+ const baseIssue = {
8
+ rule: 'NO_ACCESSIBILITY_LABEL' as const,
9
+ viewId: 1,
10
+ };
11
+
12
+ describe('AMARuleError', () => {
13
+ it('renders the severity and issue title', () => {
14
+ render(
15
+ <AMARuleError issue={baseIssue} maxHeight={400} onClose={jest.fn()} />,
16
+ );
17
+
18
+ expect(screen.getByText(/Critical/)).toBeTruthy();
19
+ expect(screen.getByText(/Missing accessibility label/i)).toBeTruthy();
20
+ });
21
+
22
+ it('renders "Why this matters" and "How to fix" sections', () => {
23
+ render(
24
+ <AMARuleError issue={baseIssue} maxHeight={400} onClose={jest.fn()} />,
25
+ );
26
+
27
+ expect(screen.getByText('Why this matters:')).toBeTruthy();
28
+ expect(screen.getByText('How to fix:')).toBeTruthy();
29
+ });
30
+
31
+ it('calls onClose when the close button is pressed', () => {
32
+ const onClose = jest.fn();
33
+ render(<AMARuleError issue={baseIssue} maxHeight={400} onClose={onClose} />);
34
+
35
+ fireEvent.press(screen.getByLabelText('Close Error information'));
36
+ expect(onClose).toHaveBeenCalledTimes(1);
37
+ });
38
+
39
+ it('renders extra text when provided', () => {
40
+ const issue = { ...baseIssue, extra: 'extra context' };
41
+ render(<AMARuleError issue={issue} maxHeight={400} onClose={jest.fn()} />);
42
+
43
+ expect(screen.getByText(/extra context/)).toBeTruthy();
44
+ });
45
+
46
+ it('renders howToFix as multiple lines when it is an array', () => {
47
+ render(
48
+ <AMARuleError
49
+ issue={{ rule: 'NO_ACCESSIBILITY_STATE_SET', viewId: 2 }}
50
+ maxHeight={400}
51
+ onClose={jest.fn()}
52
+ />,
53
+ );
54
+
55
+ expect(screen.getByText(/aria-expanded/)).toBeTruthy();
56
+ });
57
+
58
+ it('renders backtick-wrapped code segments in the how-to-fix text', () => {
59
+ render(
60
+ <AMARuleError
61
+ issue={{ rule: 'NO_ACCESSIBILITY_ROLE', viewId: 3 }}
62
+ maxHeight={400}
63
+ onClose={jest.fn()}
64
+ />,
65
+ );
66
+
67
+ expect(screen.getByText(/aria-role="button"/, { includeHiddenElements: true })).toBeTruthy();
68
+ });
69
+
70
+ it('renders contrast issue with extra color swatch text', () => {
71
+ render(
72
+ <AMARuleError
73
+ issue={{ rule: 'CONTRAST_FAILED', viewId: 4, extra: 'ratio 2.1:1 (#fff on #eee)' }}
74
+ maxHeight={400}
75
+ onClose={jest.fn()}
76
+ />,
77
+ );
78
+
79
+ expect(screen.getByText(/ratio 2.1:1/)).toBeTruthy();
80
+ });
81
+
82
+ it('renders Warning severity label', () => {
83
+ render(
84
+ <AMARuleError
85
+ issue={{ rule: 'NO_UPPERCASE_ACCESSIBILITY_LABEL', viewId: 5 }}
86
+ maxHeight={400}
87
+ onClose={jest.fn()}
88
+ />,
89
+ );
90
+
91
+ expect(screen.getByText(/Warning:/)).toBeTruthy();
92
+ });
93
+ });