@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,59 @@
1
+ beforeEach(() => {
2
+ jest.resetModules();
3
+ jest.clearAllMocks();
4
+ // @ts-ignore
5
+ global.__DEV__ = true;
6
+ });
7
+
8
+ describe('getAMARuleErrorInfo', () => {
9
+ it('returns message, url, and severity for a known rule', () => {
10
+ jest.doMock('./rules', () => ({
11
+ RULES_HELP: {
12
+ NO_ACCESSIBILITY_LABEL: {
13
+ message: 'Missing label',
14
+ url: '/guidelines/accessibility-label',
15
+ severity: 'Critical',
16
+ issue: 'Missing accessibility label',
17
+ howToFix: 'Add a label',
18
+ },
19
+ },
20
+ }));
21
+
22
+ const { getAMARuleErrorInfo } = require('./getRuleErrorInfo');
23
+ const result = getAMARuleErrorInfo({ rule: 'NO_ACCESSIBILITY_LABEL', viewId: 1 });
24
+
25
+ expect(result.message).toBe('Missing label');
26
+ expect(result.url).toContain('/guidelines/accessibility-label');
27
+ expect(result.severity).toBe('Critical');
28
+ });
29
+
30
+ it('appends extra to message when provided', () => {
31
+ jest.doMock('./rules', () => ({
32
+ RULES_HELP: {
33
+ CONTRAST_FAILED: {
34
+ message: 'Low contrast',
35
+ url: '/guidelines/contrast',
36
+ severity: 'Serious',
37
+ issue: 'Contrast issue',
38
+ howToFix: 'Fix contrast',
39
+ },
40
+ },
41
+ }));
42
+
43
+ const { getAMARuleErrorInfo } = require('./getRuleErrorInfo');
44
+ const result = getAMARuleErrorInfo({ rule: 'CONTRAST_FAILED', viewId: 2, extra: '(#fff vs #eee)' });
45
+
46
+ expect(result.message).toBe('Low contrast: (#fff vs #eee)');
47
+ });
48
+
49
+ it('throws when rule help is missing', () => {
50
+ jest.doMock('./rules', () => ({
51
+ RULES_HELP: {},
52
+ }));
53
+
54
+ const { getAMARuleErrorInfo } = require('./getRuleErrorInfo');
55
+ expect(() => getAMARuleErrorInfo({ rule: 'UNKNOWN_RULE', viewId: 3 })).toThrow(
56
+ 'Missing rule help: UNKNOWN_RULE',
57
+ );
58
+ });
59
+ });
@@ -0,0 +1,26 @@
1
+ import { AmaError } from '../types';
2
+ import { RULES_HELP } from './rules';
3
+
4
+ export type A11ySeverity = 'Serious' | 'Critical' | 'Warning';
5
+
6
+ export const getAMARuleErrorInfo = __DEV__
7
+ ? (issue: AmaError) => {
8
+ const ruleHelp = RULES_HELP![issue.rule];
9
+
10
+ if (!ruleHelp) {
11
+ console.error(`Missing rule help: ${issue.rule}`);
12
+
13
+ throw new Error(`Missing rule help: ${issue.rule}`);
14
+ }
15
+
16
+ let message = ruleHelp.message;
17
+
18
+ if (issue.extra) {
19
+ message += ': ' + issue.extra;
20
+ }
21
+
22
+ const url = `https://nearform.com/open-source/react-native-ama/${ruleHelp.url}`;
23
+
24
+ return { message, url, severity: ruleHelp.severity };
25
+ }
26
+ : null;
@@ -0,0 +1,34 @@
1
+ beforeEach(() => {
2
+ jest.resetModules();
3
+ jest.clearAllMocks();
4
+ // @ts-ignore
5
+ global.__DEV__ = true;
6
+ });
7
+
8
+ describe('shouldIgnoreContrastCheckForDisabledElement', () => {
9
+ it('returns the project config override when set', () => {
10
+ jest.doMock('../config', () => ({
11
+ __esModule: true,
12
+ default: { rules: { IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS: true } },
13
+ }));
14
+ jest.doMock('./rules', () => ({
15
+ IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS: false,
16
+ }));
17
+
18
+ const { shouldIgnoreContrastCheckForDisabledElement } = require('./ignoreContrastCheck');
19
+ expect(shouldIgnoreContrastCheckForDisabledElement()).toBe(true);
20
+ });
21
+
22
+ it('falls back to the default rule value when config has no override', () => {
23
+ jest.doMock('../config', () => ({
24
+ __esModule: true,
25
+ default: { rules: {} },
26
+ }));
27
+ jest.doMock('./rules', () => ({
28
+ IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS: false,
29
+ }));
30
+
31
+ const { shouldIgnoreContrastCheckForDisabledElement } = require('./ignoreContrastCheck');
32
+ expect(shouldIgnoreContrastCheckForDisabledElement()).toBe(false);
33
+ });
34
+ });
@@ -0,0 +1,11 @@
1
+ import projectRules from '../config';
2
+ import { IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS } from './rules';
3
+
4
+ export const shouldIgnoreContrastCheckForDisabledElement = __DEV__
5
+ ? () => {
6
+ return (
7
+ projectRules?.rules?.IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS ||
8
+ IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS
9
+ );
10
+ }
11
+ : null;
@@ -0,0 +1,176 @@
1
+ import { MOTION_ANIMATIONS } from './constants';
2
+ import { interpolateAnimationStates } from './interpolateAnimationStates';
3
+
4
+ describe('interpolateAnimationStates', () => {
5
+ describe('Given isReduceMotionEnabled is false', () => {
6
+ it('Then interpolate the animation using the "normal progress" Animated.Value', () => {
7
+ const [fakeProgress, interpolate] = createFakeProgress();
8
+ const [reduceMotionProgress, reduceMotionInterpolate] =
9
+ createFakeProgress();
10
+
11
+ const output = interpolateAnimationStates(
12
+ {
13
+ left: 100,
14
+ opacity: 1,
15
+ },
16
+ { left: 0, opacity: 0 },
17
+ false,
18
+ fakeProgress,
19
+ reduceMotionProgress,
20
+ );
21
+
22
+ expect(interpolate).toHaveBeenNthCalledWith(1, {
23
+ inputRange: [0, 1],
24
+ outputRange: [100, 0],
25
+ });
26
+
27
+ expect(interpolate).toHaveBeenNthCalledWith(2, {
28
+ inputRange: [0, 1],
29
+ outputRange: [1, 0],
30
+ });
31
+
32
+ expect(reduceMotionInterpolate).not.toHaveBeenCalled();
33
+
34
+ expect(output).toEqual({
35
+ left: 'done',
36
+ opacity: 'done',
37
+ __hasOnlyMotionAnimation: false,
38
+ });
39
+ });
40
+
41
+ it('Then interpolate arrays correctly', () => {
42
+ const [fakeProgress, interpolate] = createFakeProgress();
43
+ const [reduceMotionProgress, reduceMotionInterpolate] =
44
+ createFakeProgress();
45
+
46
+ const output = interpolateAnimationStates(
47
+ {
48
+ transform: [{ translateX: 200 }],
49
+ opacity: 1,
50
+ },
51
+ {
52
+ transform: [{ translateX: 42 }],
53
+ opacity: 0,
54
+ },
55
+ false,
56
+ fakeProgress,
57
+ reduceMotionProgress,
58
+ );
59
+
60
+ expect(interpolate).toHaveBeenNthCalledWith(1, {
61
+ inputRange: [0, 1],
62
+ outputRange: [200, 42],
63
+ });
64
+
65
+ expect(interpolate).toHaveBeenNthCalledWith(2, {
66
+ inputRange: [0, 1],
67
+ outputRange: [1, 0],
68
+ });
69
+
70
+ expect(reduceMotionInterpolate).not.toHaveBeenCalled();
71
+
72
+ expect(output).toEqual({
73
+ opacity: 'done',
74
+ transform: [{ translateX: 'done' }],
75
+ __hasOnlyMotionAnimation: false,
76
+ });
77
+ });
78
+ });
79
+
80
+ describe('Given isReduceMotionEnabled is true', () => {
81
+ it.each(MOTION_ANIMATIONS)(
82
+ 'Then interpolate the motion animations using the "reduceMotionProgress" Animated.Value',
83
+ motionAnimationKey => {
84
+ const [fakeProgress, normalInterpolate] = createFakeProgress();
85
+ const [reduceMotionProgress, reduceMotionInterpolate] =
86
+ createFakeProgress();
87
+
88
+ const initialState: Record<string, any> = {};
89
+ const finalState: Record<string, any> = {};
90
+
91
+ initialState[motionAnimationKey] = 142;
92
+ finalState[motionAnimationKey] = 42;
93
+
94
+ const output = interpolateAnimationStates(
95
+ {
96
+ ...initialState,
97
+ opacity: 0,
98
+ },
99
+ { ...finalState, opacity: 1 },
100
+ true,
101
+ fakeProgress,
102
+ reduceMotionProgress,
103
+ );
104
+
105
+ expect(normalInterpolate).toHaveBeenCalledTimes(1);
106
+ expect(normalInterpolate).toHaveBeenCalledWith({
107
+ inputRange: [0, 1],
108
+ outputRange: [0, 1],
109
+ });
110
+
111
+ expect(reduceMotionInterpolate).toHaveBeenCalledTimes(1);
112
+ expect(reduceMotionInterpolate).toHaveBeenCalledWith({
113
+ inputRange: [0, 1],
114
+ outputRange: [142, 42],
115
+ });
116
+
117
+ const expectedOutput: Record<string, any> = {
118
+ opacity: 'done',
119
+ __hasOnlyMotionAnimation: false,
120
+ };
121
+ expectedOutput[motionAnimationKey] = 'done';
122
+
123
+ expect(output).toEqual(expectedOutput);
124
+ },
125
+ );
126
+
127
+ it('Then interpolate arrays correctly', () => {
128
+ const [fakeProgress, normalInterpolate] = createFakeProgress();
129
+ const [reduceMotionProgress, reduceMotionInterpolate] =
130
+ createFakeProgress();
131
+
132
+ const output = interpolateAnimationStates(
133
+ {
134
+ transform: [{ translateX: 200 }],
135
+ opacity: 1,
136
+ },
137
+ {
138
+ transform: [{ translateX: 42 }],
139
+ opacity: 0,
140
+ },
141
+ true,
142
+ fakeProgress,
143
+ reduceMotionProgress,
144
+ );
145
+
146
+ expect(normalInterpolate).toHaveBeenCalledTimes(1);
147
+ expect(normalInterpolate).toHaveBeenCalledWith({
148
+ inputRange: [0, 1],
149
+ outputRange: [1, 0],
150
+ });
151
+
152
+ expect(reduceMotionInterpolate).toHaveBeenCalledTimes(1);
153
+ expect(reduceMotionInterpolate).toHaveBeenCalledWith({
154
+ inputRange: [0, 1],
155
+ outputRange: [200, 42],
156
+ });
157
+
158
+ expect(output).toEqual({
159
+ opacity: 'done',
160
+ transform: [{ translateX: 'done' }],
161
+ __hasOnlyMotionAnimation: false,
162
+ });
163
+ });
164
+ });
165
+ });
166
+
167
+ const createFakeProgress = () => {
168
+ const interpolateMock = jest.fn().mockReturnValue('done');
169
+
170
+ return [
171
+ {
172
+ interpolate: interpolateMock,
173
+ } as any,
174
+ interpolateMock,
175
+ ];
176
+ };
@@ -0,0 +1,55 @@
1
+ import type { Animated, ViewStyle } from 'react-native';
2
+ import { MOTION_ANIMATIONS } from './constants';
3
+
4
+ export const interpolateAnimationStates = (
5
+ from: Record<string, any>,
6
+ to: Record<string, any>,
7
+ isReduceMotionEnabled: boolean,
8
+ progressValue: Animated.Value,
9
+ reduceMotionProgressValue: Animated.Value,
10
+ ): Record<string, any> => {
11
+ return Object.keys(from).reduce(
12
+ (outputAnimation, key) => {
13
+ const isMotionAnimation = MOTION_ANIMATIONS.includes(
14
+ key as keyof ViewStyle,
15
+ );
16
+ const progressKey =
17
+ isReduceMotionEnabled && isMotionAnimation
18
+ ? reduceMotionProgressValue
19
+ : progressValue;
20
+
21
+ if (Array.isArray(from[key])) {
22
+ outputAnimation[key] = from[key].map(
23
+ (animationObject: Record<string, any>, index: number) => {
24
+ const { __hasOnlyMotionAnimation, ...style } =
25
+ interpolateAnimationStates(
26
+ animationObject,
27
+ to[key][index],
28
+ isReduceMotionEnabled,
29
+ progressValue,
30
+ reduceMotionProgressValue,
31
+ );
32
+
33
+ outputAnimation.__hasOnlyMotionAnimation =
34
+ outputAnimation.__hasOnlyMotionAnimation &&
35
+ __hasOnlyMotionAnimation;
36
+
37
+ return style;
38
+ },
39
+ );
40
+ } else {
41
+ outputAnimation[key] = progressKey.interpolate({
42
+ inputRange: [0, 1],
43
+ // @ts-ignore
44
+ outputRange: [from[key], to[key]],
45
+ });
46
+
47
+ outputAnimation.__hasOnlyMotionAnimation =
48
+ outputAnimation.__hasOnlyMotionAnimation && isMotionAnimation;
49
+ }
50
+
51
+ return outputAnimation;
52
+ },
53
+ { __hasOnlyMotionAnimation: true } as Record<string, any>,
54
+ );
55
+ };
@@ -0,0 +1,39 @@
1
+ beforeEach(() => {
2
+ jest.resetModules();
3
+ jest.clearAllMocks();
4
+ // @ts-ignore
5
+ global.__DEV__ = true;
6
+ });
7
+
8
+ describe('isAccessibilityLabelAllowed', () => {
9
+ const setupMock = (uppercaseMinLength: number, exceptions: string[] = []) => {
10
+ jest.doMock('../config', () => ({
11
+ __esModule: true,
12
+ default: { uppercaseMinLength, accessibilityLabelExceptions: exceptions },
13
+ }));
14
+ };
15
+
16
+ it('allows label shorter than uppercaseMinLength', () => {
17
+ setupMock(4);
18
+ const { isAccessibilityLabelAllowed } = require('./isA11yLabelAllowed');
19
+ expect(isAccessibilityLabelAllowed('OK')).toBe(true);
20
+ });
21
+
22
+ it('disallows label at or above uppercaseMinLength', () => {
23
+ setupMock(4);
24
+ const { isAccessibilityLabelAllowed } = require('./isA11yLabelAllowed');
25
+ expect(isAccessibilityLabelAllowed('LONG')).toBe(false);
26
+ });
27
+
28
+ it('allows label that is in the exceptions list', () => {
29
+ setupMock(4, ['FAQ']);
30
+ const { isAccessibilityLabelAllowed } = require('./isA11yLabelAllowed');
31
+ expect(isAccessibilityLabelAllowed('FAQ')).toBe(true);
32
+ });
33
+
34
+ it('disallows label that is not in exceptions and exceeds min length', () => {
35
+ setupMock(4, ['FAQ']);
36
+ const { isAccessibilityLabelAllowed } = require('./isA11yLabelAllowed');
37
+ expect(isAccessibilityLabelAllowed('LONG')).toBe(false);
38
+ });
39
+ });
@@ -0,0 +1,10 @@
1
+ import projectRules from '../config';
2
+
3
+ export const isAccessibilityLabelAllowed = __DEV__
4
+ ? (accessibilityLabel: string) => {
5
+ return (
6
+ accessibilityLabel.length < projectRules.uppercaseMinLength ||
7
+ projectRules?.accessibilityLabelExceptions?.includes(accessibilityLabel)
8
+ );
9
+ }
10
+ : null;
@@ -0,0 +1,35 @@
1
+ beforeEach(() => {
2
+ jest.resetModules();
3
+ jest.clearAllMocks();
4
+ // @ts-ignore
5
+ global.__DEV__ = true;
6
+ });
7
+
8
+ describe('isRuleDisabled', () => {
9
+ it('returns true when the rule action is PLEASE_FORGIVE_ME', () => {
10
+ jest.doMock('./getRuleAction', () => ({
11
+ getRuleAction: () => 'PLEASE_FORGIVE_ME',
12
+ }));
13
+
14
+ const { isRuleDisabled } = require('./isRuleDisabled');
15
+ expect(isRuleDisabled({ rule: 'CONTRAST_FAILED', viewId: 1 })).toBe(true);
16
+ });
17
+
18
+ it('returns false when the rule action is MUST', () => {
19
+ jest.doMock('./getRuleAction', () => ({
20
+ getRuleAction: () => 'MUST',
21
+ }));
22
+
23
+ const { isRuleDisabled } = require('./isRuleDisabled');
24
+ expect(isRuleDisabled({ rule: 'NO_ACCESSIBILITY_LABEL', viewId: 1 })).toBe(false);
25
+ });
26
+
27
+ it('returns false when the rule action is SHOULD', () => {
28
+ jest.doMock('./getRuleAction', () => ({
29
+ getRuleAction: () => 'SHOULD',
30
+ }));
31
+
32
+ const { isRuleDisabled } = require('./isRuleDisabled');
33
+ expect(isRuleDisabled({ rule: 'CONTRAST_FAILED_AAA', viewId: 1 })).toBe(false);
34
+ });
35
+ });
@@ -0,0 +1,10 @@
1
+ import { AmaError } from '../types';
2
+ import { getRuleAction } from './getRuleAction';
3
+
4
+ export const isRuleDisabled = __DEV__
5
+ ? (error: AmaError) => {
6
+ const action = getRuleAction?.(error.rule);
7
+
8
+ return action && action === 'PLEASE_FORGIVE_ME';
9
+ }
10
+ : null;
@@ -0,0 +1,148 @@
1
+ beforeEach(() => {
2
+ jest.resetModules();
3
+ jest.clearAllMocks();
4
+ // @ts-ignore
5
+ global.__DEV__ = true;
6
+ });
7
+
8
+ describe('logError', () => {
9
+ it('calls logger.error for a MUST rule', () => {
10
+ const loggerError = jest.fn();
11
+ const loggerWarn = jest.fn();
12
+
13
+ jest.doMock('./logger', () => ({
14
+ __esModule: true,
15
+ default: { log: jest.fn(), warn: loggerWarn, error: loggerError },
16
+ }));
17
+ jest.doMock('./getRuleAction', () => ({
18
+ getRuleAction: () => 'MUST',
19
+ }));
20
+ jest.doMock('./rules', () => ({
21
+ RULES_HELP: {
22
+ NO_ACCESSIBILITY_LABEL: {
23
+ message: 'Missing label',
24
+ url: '/guidelines/accessibility-label',
25
+ severity: 'Critical',
26
+ },
27
+ },
28
+ }));
29
+
30
+ const { logError } = require('./logError');
31
+ logError({ rule: 'NO_ACCESSIBILITY_LABEL', viewId: 1 });
32
+
33
+ expect(loggerError).toHaveBeenCalledTimes(1);
34
+ expect(loggerWarn).not.toHaveBeenCalled();
35
+ });
36
+
37
+ it('calls logger.error for a MUST_NOT rule', () => {
38
+ const loggerError = jest.fn();
39
+ const loggerWarn = jest.fn();
40
+
41
+ jest.doMock('./logger', () => ({
42
+ __esModule: true,
43
+ default: { log: jest.fn(), warn: loggerWarn, error: loggerError },
44
+ }));
45
+ jest.doMock('./getRuleAction', () => ({
46
+ getRuleAction: () => 'MUST_NOT',
47
+ }));
48
+ jest.doMock('./rules', () => ({
49
+ RULES_HELP: {
50
+ NO_UPPERCASE_TEXT: {
51
+ message: 'Avoid uppercase',
52
+ url: '/guidelines/text',
53
+ severity: 'Serious',
54
+ },
55
+ },
56
+ }));
57
+
58
+ const { logError } = require('./logError');
59
+ logError({ rule: 'NO_UPPERCASE_TEXT', viewId: 2 });
60
+
61
+ expect(loggerError).toHaveBeenCalledTimes(1);
62
+ expect(loggerWarn).not.toHaveBeenCalled();
63
+ });
64
+
65
+ it('calls logger.warn for a SHOULD rule', () => {
66
+ const loggerError = jest.fn();
67
+ const loggerWarn = jest.fn();
68
+
69
+ jest.doMock('./logger', () => ({
70
+ __esModule: true,
71
+ default: { log: jest.fn(), warn: loggerWarn, error: loggerError },
72
+ }));
73
+ jest.doMock('./getRuleAction', () => ({
74
+ getRuleAction: () => 'SHOULD',
75
+ }));
76
+ jest.doMock('./rules', () => ({
77
+ RULES_HELP: {
78
+ CONTRAST_FAILED_AAA: {
79
+ message: 'Low contrast AAA',
80
+ url: '/guidelines/contrast',
81
+ severity: 'Serious',
82
+ },
83
+ },
84
+ }));
85
+
86
+ const { logError } = require('./logError');
87
+ logError({ rule: 'CONTRAST_FAILED_AAA', viewId: 3 });
88
+
89
+ expect(loggerWarn).toHaveBeenCalledTimes(1);
90
+ expect(loggerError).not.toHaveBeenCalled();
91
+ });
92
+
93
+ it('includes the label in the log when provided', () => {
94
+ const loggerError = jest.fn();
95
+
96
+ jest.doMock('./logger', () => ({
97
+ __esModule: true,
98
+ default: { log: jest.fn(), warn: jest.fn(), error: loggerError },
99
+ }));
100
+ jest.doMock('./getRuleAction', () => ({
101
+ getRuleAction: () => 'MUST',
102
+ }));
103
+ jest.doMock('./rules', () => ({
104
+ RULES_HELP: {
105
+ NO_ACCESSIBILITY_LABEL: {
106
+ message: 'Missing label',
107
+ url: '/guidelines/accessibility-label',
108
+ severity: 'Critical',
109
+ },
110
+ },
111
+ }));
112
+
113
+ const { logError } = require('./logError');
114
+ logError({ rule: 'NO_ACCESSIBILITY_LABEL', viewId: 5, label: 'Submit' });
115
+
116
+ expect(loggerError).toHaveBeenCalledWith(
117
+ expect.stringContaining('Submit'),
118
+ );
119
+ });
120
+
121
+ it('includes extra in the message when provided', () => {
122
+ const loggerError = jest.fn();
123
+
124
+ jest.doMock('./logger', () => ({
125
+ __esModule: true,
126
+ default: { log: jest.fn(), warn: jest.fn(), error: loggerError },
127
+ }));
128
+ jest.doMock('./getRuleAction', () => ({
129
+ getRuleAction: () => 'MUST',
130
+ }));
131
+ jest.doMock('./rules', () => ({
132
+ RULES_HELP: {
133
+ NO_ACCESSIBILITY_LABEL: {
134
+ message: 'Missing label',
135
+ url: '/guidelines/accessibility-label',
136
+ severity: 'Critical',
137
+ },
138
+ },
139
+ }));
140
+
141
+ const { logError } = require('./logError');
142
+ logError({ rule: 'NO_ACCESSIBILITY_LABEL', viewId: 6, extra: 'extra info' });
143
+
144
+ expect(loggerError).toHaveBeenCalledWith(
145
+ expect.stringContaining('extra info'),
146
+ );
147
+ });
148
+ });
@@ -0,0 +1,41 @@
1
+ import { AmaError } from '../types';
2
+ import { getRuleAction } from './getRuleAction';
3
+ import logger from './logger';
4
+ import { RULES_HELP } from './rules';
5
+
6
+ const getAMARuleErrorInfo = __DEV__
7
+ ? ({ rule, extra }: AmaError) => {
8
+ const ruleHelp = RULES_HELP![rule];
9
+
10
+ let message = ruleHelp.message;
11
+
12
+ if (extra) {
13
+ message += ': ' + extra;
14
+ }
15
+
16
+ const url = `https://nearform.com/open-source/react-native-ama/${ruleHelp.url}`;
17
+
18
+ return { message, url, severity: ruleHelp.severity };
19
+ }
20
+ : null;
21
+
22
+ export const logError = __DEV__
23
+ ? (issue: AmaError) => {
24
+ const action = getRuleAction?.(issue.rule);
25
+ const log =
26
+ action === 'MUST' || action === 'MUST_NOT'
27
+ ? logger?.error
28
+ : logger?.warn;
29
+
30
+ const { message, url } = getAMARuleErrorInfo?.(issue) ?? {};
31
+
32
+ log?.(
33
+ `${issue.rule}
34
+ Component: ${issue.label || '--- NO LABEL FOUND ---'} (#${issue.viewId})
35
+
36
+ ${message}
37
+
38
+ Learn about: ${url}\n`,
39
+ );
40
+ }
41
+ : null;