@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.
- 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,98 @@
|
|
|
1
|
+
describe('logFoundIssues', () => {
|
|
2
|
+
beforeEach(() => {
|
|
3
|
+
jest.resetModules();
|
|
4
|
+
jest.clearAllMocks();
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
global.__DEV__ = true;
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('logs only the summary when log mode is inspect', () => {
|
|
10
|
+
const loggerLog = jest.fn();
|
|
11
|
+
const logError = jest.fn();
|
|
12
|
+
|
|
13
|
+
jest.doMock('../config', () => ({
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: {
|
|
16
|
+
log: 'inspect',
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
jest.doMock('./logger', () => ({
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: {
|
|
22
|
+
log: loggerLog,
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
jest.doMock('./logError', () => ({
|
|
26
|
+
logError,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const { logFoundIssues } = require('./logFoundIssues');
|
|
30
|
+
|
|
31
|
+
logFoundIssues([{ rule: 'NO_HEADER_FOUND', viewId: -1 }]);
|
|
32
|
+
|
|
33
|
+
expect(loggerLog).toHaveBeenCalledWith('Issues found: 1');
|
|
34
|
+
expect(logError).not.toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('logs every issue when log mode is always', () => {
|
|
38
|
+
const loggerLog = jest.fn();
|
|
39
|
+
const logError = jest.fn();
|
|
40
|
+
const issues = [
|
|
41
|
+
{ rule: 'NO_HEADER_FOUND', viewId: -1 },
|
|
42
|
+
{ rule: 'NO_ACCESSIBILITY_ROLE', viewId: 42, label: 'Checkout' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
jest.doMock('../config', () => ({
|
|
46
|
+
__esModule: true,
|
|
47
|
+
default: {
|
|
48
|
+
log: 'always',
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
jest.doMock('./logger', () => ({
|
|
52
|
+
__esModule: true,
|
|
53
|
+
default: {
|
|
54
|
+
log: loggerLog,
|
|
55
|
+
},
|
|
56
|
+
}));
|
|
57
|
+
jest.doMock('./logError', () => ({
|
|
58
|
+
logError,
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
const { logFoundIssues } = require('./logFoundIssues');
|
|
62
|
+
|
|
63
|
+
logFoundIssues(issues);
|
|
64
|
+
|
|
65
|
+
expect(loggerLog).toHaveBeenCalledWith('Issues found: 2');
|
|
66
|
+
expect(logError).toHaveBeenCalledTimes(2);
|
|
67
|
+
expect(logError).toHaveBeenNthCalledWith(1, issues[0]);
|
|
68
|
+
expect(logError).toHaveBeenNthCalledWith(2, issues[1]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('does nothing when there are no issues', () => {
|
|
72
|
+
const loggerLog = jest.fn();
|
|
73
|
+
const logError = jest.fn();
|
|
74
|
+
|
|
75
|
+
jest.doMock('../config', () => ({
|
|
76
|
+
__esModule: true,
|
|
77
|
+
default: {
|
|
78
|
+
log: 'always',
|
|
79
|
+
},
|
|
80
|
+
}));
|
|
81
|
+
jest.doMock('./logger', () => ({
|
|
82
|
+
__esModule: true,
|
|
83
|
+
default: {
|
|
84
|
+
log: loggerLog,
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
jest.doMock('./logError', () => ({
|
|
88
|
+
logError,
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
const { logFoundIssues } = require('./logFoundIssues');
|
|
92
|
+
|
|
93
|
+
logFoundIssues([]);
|
|
94
|
+
|
|
95
|
+
expect(loggerLog).not.toHaveBeenCalled();
|
|
96
|
+
expect(logError).not.toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import projectRules from '../config';
|
|
2
|
+
import { AmaError } from '../types';
|
|
3
|
+
import { logError } from './logError';
|
|
4
|
+
import logger from './logger';
|
|
5
|
+
|
|
6
|
+
export const logFoundIssues = __DEV__
|
|
7
|
+
? (issues: AmaError[]) => {
|
|
8
|
+
if (issues.length === 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
logger?.log(`Issues found: ${issues.length}`);
|
|
13
|
+
|
|
14
|
+
if (projectRules.log !== 'always') {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
issues.forEach((issue) => {
|
|
19
|
+
logError?.(issue);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
: null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getRuleAction } from './getRuleAction';
|
|
2
|
+
import { LOGGER_RULES, NON_OVERRIDABLE_RULES } from './rules';
|
|
3
|
+
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
jest.clearAllMocks();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe('getRuleAction', () => {
|
|
9
|
+
it.each(Object.keys(LOGGER_RULES!))(
|
|
10
|
+
'returns the default rule action for %s when no custom config is defined',
|
|
11
|
+
(ruleKey) => {
|
|
12
|
+
const result = getRuleAction?.(ruleKey as any);
|
|
13
|
+
|
|
14
|
+
expect(result).toBe(LOGGER_RULES![ruleKey as keyof typeof LOGGER_RULES]);
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
it.each(NON_OVERRIDABLE_RULES!)(
|
|
19
|
+
'returns the default (non-overridable) action for rule %s regardless of config',
|
|
20
|
+
(rule) => {
|
|
21
|
+
const result = getRuleAction?.(rule as any);
|
|
22
|
+
|
|
23
|
+
expect(result).toBe(LOGGER_RULES![rule as keyof typeof LOGGER_RULES]);
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const _log = __DEV__
|
|
2
|
+
? (consoleMethod: Function, msg: string) => {
|
|
3
|
+
consoleMethod(`[React Native AMA]: ${msg}`);
|
|
4
|
+
}
|
|
5
|
+
: null;
|
|
6
|
+
|
|
7
|
+
const logger = _log
|
|
8
|
+
? {
|
|
9
|
+
log: (msg: string) => _log(console.debug, msg),
|
|
10
|
+
warn: (msg: string) => _log(console.warn, msg),
|
|
11
|
+
error: (msg: string) => _log(console.error, msg),
|
|
12
|
+
}
|
|
13
|
+
: null;
|
|
14
|
+
|
|
15
|
+
export default logger;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
beforeEach(() => {
|
|
2
|
+
jest.resetModules();
|
|
3
|
+
jest.clearAllMocks();
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
global.__DEV__ = true;
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe('logger', () => {
|
|
9
|
+
it('logger.log calls console.debug with the AMA prefix', () => {
|
|
10
|
+
const spy = jest.spyOn(console, 'debug').mockImplementation();
|
|
11
|
+
const logger = require('./logger').default;
|
|
12
|
+
|
|
13
|
+
logger.log('hello');
|
|
14
|
+
|
|
15
|
+
expect(spy).toHaveBeenCalledWith('[React Native AMA]: hello');
|
|
16
|
+
spy.mockRestore();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('logger.warn calls console.warn with the AMA prefix', () => {
|
|
20
|
+
const spy = jest.spyOn(console, 'warn').mockImplementation();
|
|
21
|
+
const logger = require('./logger').default;
|
|
22
|
+
|
|
23
|
+
logger.warn('something wrong');
|
|
24
|
+
|
|
25
|
+
expect(spy).toHaveBeenCalledWith('[React Native AMA]: something wrong');
|
|
26
|
+
spy.mockRestore();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('logger.error calls console.error with the AMA prefix', () => {
|
|
30
|
+
const spy = jest.spyOn(console, 'error').mockImplementation();
|
|
31
|
+
const logger = require('./logger').default;
|
|
32
|
+
|
|
33
|
+
logger.error('critical error');
|
|
34
|
+
|
|
35
|
+
expect(spy).toHaveBeenCalledWith('[React Native AMA]: critical error');
|
|
36
|
+
spy.mockRestore();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
jest.mock('react-native', () => ({
|
|
2
|
+
Platform: {
|
|
3
|
+
OS: 'ios',
|
|
4
|
+
},
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
describe('minimumTouchableSize', () => {
|
|
8
|
+
it.each`
|
|
9
|
+
os | minimumSize
|
|
10
|
+
${'ios'} | ${44}
|
|
11
|
+
${'android'} | ${48}
|
|
12
|
+
`(
|
|
13
|
+
'returns the correct minimum touchable area size for $os',
|
|
14
|
+
({ os, minimumSize }) => {
|
|
15
|
+
jest.resetModules();
|
|
16
|
+
|
|
17
|
+
jest.doMock('react-native', () => ({
|
|
18
|
+
Platform: { OS: os },
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const { MINIMUM_TOUCHABLE_SIZE } = require('./minimumTouchableSize');
|
|
22
|
+
|
|
23
|
+
expect(MINIMUM_TOUCHABLE_SIZE).toEqual(minimumSize);
|
|
24
|
+
|
|
25
|
+
jest.dontMock('react-native');
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const ANDROID_MINIMUM_TOUCHABLE_SIZE = 48;
|
|
4
|
+
const IOS_MINIMUM_TOUCHABLE_SIZE = 44;
|
|
5
|
+
|
|
6
|
+
export const MINIMUM_TOUCHABLE_SIZE =
|
|
7
|
+
Platform.OS === 'android'
|
|
8
|
+
? ANDROID_MINIMUM_TOUCHABLE_SIZE
|
|
9
|
+
: IOS_MINIMUM_TOUCHABLE_SIZE;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IS_IOS, IS_ANDROID } from './platformHelpers';
|
|
2
|
+
|
|
3
|
+
describe('platformHelpers', () => {
|
|
4
|
+
it('IS_IOS is true when Platform.OS is ios (default mock)', () => {
|
|
5
|
+
expect(IS_IOS).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('IS_ANDROID is false when Platform.OS is ios (default mock)', () => {
|
|
9
|
+
expect(IS_ANDROID).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type AmaRule =
|
|
4
|
+
| 'BOTTOM_SHEET_CLOSE_ACTION'
|
|
5
|
+
| 'CONTRAST_FAILED'
|
|
6
|
+
| 'CONTRAST_FAILED_AAA'
|
|
7
|
+
| 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE'
|
|
8
|
+
| 'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE'
|
|
9
|
+
| 'IMAGE_MISSING_ALT_TEXT'
|
|
10
|
+
| 'MINIMUM_SIZE'
|
|
11
|
+
| 'NO_ACCESSIBILITY_LABEL'
|
|
12
|
+
| 'NO_ACCESSIBILITY_ROLE'
|
|
13
|
+
| 'NO_FORM_ERROR'
|
|
14
|
+
| 'INPUT_HAS_NO_VISIBLE_LABEL'
|
|
15
|
+
| 'NO_KEYBOARD_TRAP'
|
|
16
|
+
| 'NO_UNDEFINED'
|
|
17
|
+
| 'NO_UPPERCASE_TEXT'
|
|
18
|
+
| 'INCOMPATIBLE_ACCESSIBILITY_STATE'
|
|
19
|
+
| 'INCOMPATIBLE_ACCESSIBILITY_ROLE'
|
|
20
|
+
| 'INPUT_HAS_NO_VISIBLE_LABEL_ENDING_WITH_ASTERISK'
|
|
21
|
+
| 'UPPERCASE_TEXT_NO_ACCESSIBILITY_LABEL'
|
|
22
|
+
| 'NO_UPPERCASE_ACCESSIBILITY_LABEL'
|
|
23
|
+
| 'NO_HEADER_FOUND'
|
|
24
|
+
| 'NO_ACCESSIBILITY_STATE_SET'
|
|
25
|
+
| 'INPUT_INVALID_RETURN_KEY'
|
|
26
|
+
| 'INPUT_HAS_FOCUSABLE_LABEL'
|
|
27
|
+
| 'LONG_NUMBER_NOT_FORMATTED';
|
|
28
|
+
|
|
29
|
+
export type AmaRuleAction =
|
|
30
|
+
| 'SHOULD_NOT'
|
|
31
|
+
| 'MUST_NOT'
|
|
32
|
+
| 'MUST'
|
|
33
|
+
| 'SHOULD'
|
|
34
|
+
| 'PLEASE_FORGIVE_ME';
|
|
35
|
+
|
|
36
|
+
export const NON_OVERRIDABLE_RULES: Array<AmaRule> | undefined = __DEV__
|
|
37
|
+
? [
|
|
38
|
+
'NO_ACCESSIBILITY_ROLE',
|
|
39
|
+
'NO_ACCESSIBILITY_LABEL',
|
|
40
|
+
'NO_KEYBOARD_TRAP',
|
|
41
|
+
'NO_UNDEFINED',
|
|
42
|
+
'INPUT_HAS_NO_VISIBLE_LABEL',
|
|
43
|
+
'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE',
|
|
44
|
+
'BOTTOM_SHEET_CLOSE_ACTION',
|
|
45
|
+
'INCOMPATIBLE_ACCESSIBILITY_STATE',
|
|
46
|
+
'INCOMPATIBLE_ACCESSIBILITY_ROLE',
|
|
47
|
+
'NO_HEADER_FOUND',
|
|
48
|
+
]
|
|
49
|
+
: undefined;
|
|
50
|
+
|
|
51
|
+
export const LOGGER_RULES: Record<AmaRule, AmaRuleAction> | null = __DEV__
|
|
52
|
+
? {
|
|
53
|
+
CONTRAST_FAILED: 'MUST',
|
|
54
|
+
CONTRAST_FAILED_AAA: 'SHOULD',
|
|
55
|
+
FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE: 'SHOULD',
|
|
56
|
+
FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE: 'MUST',
|
|
57
|
+
IMAGE_MISSING_ALT_TEXT: 'MUST',
|
|
58
|
+
MINIMUM_SIZE: 'MUST',
|
|
59
|
+
NO_ACCESSIBILITY_LABEL: 'MUST',
|
|
60
|
+
NO_ACCESSIBILITY_ROLE: 'MUST',
|
|
61
|
+
INPUT_HAS_NO_VISIBLE_LABEL: 'MUST',
|
|
62
|
+
NO_FORM_ERROR: 'MUST',
|
|
63
|
+
NO_KEYBOARD_TRAP: 'MUST_NOT',
|
|
64
|
+
NO_UNDEFINED: 'MUST_NOT',
|
|
65
|
+
UPPERCASE_TEXT_NO_ACCESSIBILITY_LABEL: 'MUST_NOT',
|
|
66
|
+
NO_UPPERCASE_TEXT: 'MUST_NOT',
|
|
67
|
+
BOTTOM_SHEET_CLOSE_ACTION: 'MUST',
|
|
68
|
+
INCOMPATIBLE_ACCESSIBILITY_STATE: 'MUST',
|
|
69
|
+
INPUT_HAS_NO_VISIBLE_LABEL_ENDING_WITH_ASTERISK: 'MUST_NOT',
|
|
70
|
+
INCOMPATIBLE_ACCESSIBILITY_ROLE: 'MUST_NOT',
|
|
71
|
+
NO_UPPERCASE_ACCESSIBILITY_LABEL: 'SHOULD_NOT',
|
|
72
|
+
NO_HEADER_FOUND: 'MUST',
|
|
73
|
+
NO_ACCESSIBILITY_STATE_SET: 'MUST',
|
|
74
|
+
INPUT_INVALID_RETURN_KEY: 'MUST',
|
|
75
|
+
INPUT_HAS_FOCUSABLE_LABEL: 'MUST',
|
|
76
|
+
LONG_NUMBER_NOT_FORMATTED: 'SHOULD_NOT',
|
|
77
|
+
}
|
|
78
|
+
: null;
|
|
79
|
+
|
|
80
|
+
export const CONTRAST_CHECKER_MAX_DEPTH = 5;
|
|
81
|
+
export const IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS = false;
|
|
82
|
+
|
|
83
|
+
export type A11ySeverity = 'Serious' | 'Critical' | 'Warning';
|
|
84
|
+
|
|
85
|
+
export type RuleHelp = {
|
|
86
|
+
[key in AmaRule]: {
|
|
87
|
+
url: string;
|
|
88
|
+
issue: string;
|
|
89
|
+
severity: A11ySeverity;
|
|
90
|
+
message: string;
|
|
91
|
+
howToFix: string | string[];
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const RULES_HELP: RuleHelp | null = __DEV__
|
|
96
|
+
? {
|
|
97
|
+
CONTRAST_FAILED: {
|
|
98
|
+
url: '/guidelines/contrast',
|
|
99
|
+
issue: 'Insufficient color contrast',
|
|
100
|
+
severity: 'Serious',
|
|
101
|
+
message:
|
|
102
|
+
'Text does not have enough contrast against its background. Low contrast makes content hard or impossible to read for users with low vision or when viewing the screen in poor lighting.',
|
|
103
|
+
howToFix:
|
|
104
|
+
'Increase the contrast between text and background to meet WCAG AA requirements.',
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
CONTRAST_FAILED_AAA: {
|
|
108
|
+
url: '/guidelines/contrast',
|
|
109
|
+
issue: 'Insufficient color contrast (AAA)',
|
|
110
|
+
severity: 'Serious',
|
|
111
|
+
message:
|
|
112
|
+
'Text does not meet enhanced contrast requirements. This affects users with more severe visual impairments and reduces overall readability.',
|
|
113
|
+
howToFix:
|
|
114
|
+
'Increase the contrast between text and background to meet WCAG AAA requirements.',
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
MINIMUM_SIZE: {
|
|
118
|
+
url: '/guidelines/minimum-size',
|
|
119
|
+
issue: 'Touch target too small',
|
|
120
|
+
severity: 'Serious',
|
|
121
|
+
message:
|
|
122
|
+
'The element is too small to be tapped reliably. Small touch targets increase error rates, especially for users with motor impairments or larger fingers.',
|
|
123
|
+
howToFix: `Ensure the element is at least ${Platform.select({
|
|
124
|
+
ios: '44x44',
|
|
125
|
+
default: '48x48',
|
|
126
|
+
})} points, or increase the tappable area using hitSlop.`,
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
UPPERCASE_TEXT_NO_ACCESSIBILITY_LABEL: {
|
|
130
|
+
url: '/guidelines/text',
|
|
131
|
+
issue: 'Uppercase text without accessibility label',
|
|
132
|
+
severity: 'Serious',
|
|
133
|
+
message:
|
|
134
|
+
'All caps text can be read letter by letter by screen readers, making it hard to understand. It can also make voice commands less predictable.',
|
|
135
|
+
howToFix: 'Provide an accessibility label with normal capitalization.',
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
NO_UPPERCASE_TEXT: {
|
|
139
|
+
url: '/guidelines/text',
|
|
140
|
+
issue: 'Avoid uppercase text',
|
|
141
|
+
severity: 'Serious',
|
|
142
|
+
message:
|
|
143
|
+
'Uppercase text can reduce readability for both screen reader users and sighted users.',
|
|
144
|
+
howToFix:
|
|
145
|
+
'Use normal capitalization or provide a properly capitalized accessibility label.',
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
NO_UPPERCASE_ACCESSIBILITY_LABEL: {
|
|
149
|
+
url: '/guidelines/accessibility-label#avoid-all-caps-accessibility-label',
|
|
150
|
+
issue: 'Uppercase accessibility label',
|
|
151
|
+
severity: 'Warning',
|
|
152
|
+
message:
|
|
153
|
+
'Accessibility labels in all caps may be read incorrectly by screen readers.',
|
|
154
|
+
howToFix: 'Use normal capitalization in accessibility labels.',
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
NO_ACCESSIBILITY_LABEL: {
|
|
158
|
+
url: '/guidelines/accessibility-label',
|
|
159
|
+
issue: 'Missing accessibility label',
|
|
160
|
+
severity: 'Critical',
|
|
161
|
+
message:
|
|
162
|
+
'This element has no accessible name. Screen reader users will not know what this control does, and Voice Control users will not be able to target it with voice commands.',
|
|
163
|
+
howToFix: [
|
|
164
|
+
'Add a descriptive accessibility label that explains the action or purpose, for example:',
|
|
165
|
+
' • `aria-label="Add to cart"`',
|
|
166
|
+
' • `aria-label="Go back"`',
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
NO_ACCESSIBILITY_ROLE: {
|
|
171
|
+
url: '/guidelines/accessibility-role',
|
|
172
|
+
issue: 'Missing accessibility role',
|
|
173
|
+
severity: 'Critical',
|
|
174
|
+
message:
|
|
175
|
+
'Without a role, assistive technologies cannot determine how this element should be used. Voice Control may also misinterpret how to interact with it.',
|
|
176
|
+
howToFix: [
|
|
177
|
+
'Assign the correct accessibility role, for example:',
|
|
178
|
+
' • Button: `aria-role="button"`',
|
|
179
|
+
' • Switch: `aria-role="switch"`',
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
NO_KEYBOARD_TRAP: {
|
|
184
|
+
url: '/guidelines/forms',
|
|
185
|
+
issue: 'Keyboard trap detected',
|
|
186
|
+
severity: 'Critical',
|
|
187
|
+
message:
|
|
188
|
+
'Focus cannot escape this input. Users navigating with a keyboard or assistive technology may get stuck.',
|
|
189
|
+
howToFix:
|
|
190
|
+
'Ensure users can move focus away from the field even when validation fails.',
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
INPUT_HAS_NO_VISIBLE_LABEL: {
|
|
194
|
+
url: '/guidelines/forms#labels',
|
|
195
|
+
issue: 'Missing form label',
|
|
196
|
+
severity: 'Critical',
|
|
197
|
+
message:
|
|
198
|
+
'This input has no visible label. Without a persistent label, users can forget what the field is for once they start typing, increasing errors and frustration. Placeholders are not a replacement for labels, as they disappear once text is entered.',
|
|
199
|
+
howToFix:
|
|
200
|
+
"Add a visible, persistent label that clearly explains the field's purpose.",
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
NO_FORM_ERROR: {
|
|
204
|
+
url: '/guidelines/forms',
|
|
205
|
+
issue: 'Missing form error handling',
|
|
206
|
+
severity: 'Serious',
|
|
207
|
+
message:
|
|
208
|
+
'After submission fails, users are not informed where the error occurred. Screen reader users receive no feedback.',
|
|
209
|
+
howToFix:
|
|
210
|
+
'Provide clear error messages and move focus to the first invalid field.',
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
INPUT_HAS_NO_VISIBLE_LABEL_ENDING_WITH_ASTERISK: {
|
|
214
|
+
url: '/guidelines/forms#labels',
|
|
215
|
+
issue: 'Asterisk used to indicate required field',
|
|
216
|
+
severity: 'Serious',
|
|
217
|
+
message:
|
|
218
|
+
'Asterisks are visual only. Screen readers do not reliably convey that the field is required.',
|
|
219
|
+
howToFix:
|
|
220
|
+
'Mark required fields programmatically instead of relying on an asterisk.',
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE: {
|
|
224
|
+
url: '/guidelines/lists-grids#number-of-results',
|
|
225
|
+
issue: 'Missing item count in list announcement',
|
|
226
|
+
severity: 'Serious',
|
|
227
|
+
message:
|
|
228
|
+
'List announcements without item counts provide poor context for screen reader users.',
|
|
229
|
+
howToFix:
|
|
230
|
+
'Include the number of items in the announcement, even for a single result.',
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE: {
|
|
234
|
+
url: '/guidelines/lists-grids#number-of-results',
|
|
235
|
+
issue: 'Missing item count in list announcement',
|
|
236
|
+
severity: 'Serious',
|
|
237
|
+
message:
|
|
238
|
+
'List announcements without item counts provide poor context for screen reader users.',
|
|
239
|
+
howToFix: 'Include the total number of items in the announcement.',
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
BOTTOM_SHEET_CLOSE_ACTION: {
|
|
243
|
+
url: '/guidelines/bottomsheet',
|
|
244
|
+
issue: 'No accessible close action',
|
|
245
|
+
severity: 'Critical',
|
|
246
|
+
message:
|
|
247
|
+
'Users must have a clear and accessible way to dismiss a bottom sheet. Without it, Voice Control users may be unable to close the view.',
|
|
248
|
+
howToFix:
|
|
249
|
+
'Provide a close button or gesture that is accessible to screen readers.',
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
INCOMPATIBLE_ACCESSIBILITY_STATE: {
|
|
253
|
+
url: '/guidelines/accessibility-role',
|
|
254
|
+
issue: 'Incompatible accessibility state',
|
|
255
|
+
severity: 'Serious',
|
|
256
|
+
message:
|
|
257
|
+
'The accessibility state does not match the element’s behavior.',
|
|
258
|
+
howToFix:
|
|
259
|
+
'Use accessibility states that are valid for the given element type.',
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
INCOMPATIBLE_ACCESSIBILITY_ROLE: {
|
|
263
|
+
url: '/guidelines/accessibility-role',
|
|
264
|
+
issue: 'Incompatible accessibility role',
|
|
265
|
+
severity: 'Serious',
|
|
266
|
+
message: 'The assigned role does not match how this element behaves.',
|
|
267
|
+
howToFix:
|
|
268
|
+
'Update the role to reflect the element’s actual interaction pattern.',
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
NO_HEADER_FOUND: {
|
|
272
|
+
url: '/guidelines/headers',
|
|
273
|
+
issue: 'Missing header semantics',
|
|
274
|
+
severity: 'Serious',
|
|
275
|
+
message:
|
|
276
|
+
'Screen reader users rely on headers to navigate content efficiently. Headers also help Voice Control users jump to sections by name.',
|
|
277
|
+
howToFix: 'Mark visual headers with `aria-role="header"`.',
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
NO_ACCESSIBILITY_STATE_SET: {
|
|
281
|
+
url: '/guidelines/accessibility-states',
|
|
282
|
+
issue: 'Accessibility state not updated',
|
|
283
|
+
severity: 'Critical',
|
|
284
|
+
message:
|
|
285
|
+
'The UI state changed, but assistive technologies were not informed.',
|
|
286
|
+
howToFix: [
|
|
287
|
+
'Update accessibilityState to reflect the new state, for example:',
|
|
288
|
+
' • `aria-expanded={expanded}`',
|
|
289
|
+
' • `aria-checked={checked}`',
|
|
290
|
+
' • `aria-selected={selected}`',
|
|
291
|
+
' • `aria-busy={busy}`',
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
INPUT_INVALID_RETURN_KEY: {
|
|
296
|
+
url: '/guidelines/forms#focus-on-the-next-field',
|
|
297
|
+
issue: 'Invalid return key behavior',
|
|
298
|
+
severity: 'Serious',
|
|
299
|
+
message:
|
|
300
|
+
'By default, React Native shows the `Return` keyboard action that dismisses the keyboard. This interrupts form completion and forces users to manually navigate to the next field.',
|
|
301
|
+
howToFix:
|
|
302
|
+
'Explicitly set the appropriate `returnType` (for example `next`) and handle `onSubmitEditing` to move focus to the next input instead of dismissing the keyboard.',
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
INPUT_HAS_FOCUSABLE_LABEL: {
|
|
306
|
+
url: '/guidelines/forms#grouping',
|
|
307
|
+
issue: 'Label is focusable',
|
|
308
|
+
severity: 'Serious',
|
|
309
|
+
message:
|
|
310
|
+
'The label receives focus separately, resulting in duplicate announcements.',
|
|
311
|
+
howToFix:
|
|
312
|
+
'Make the label non-focusable and provide the label via `aria-label` on the input.',
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
IMAGE_MISSING_ALT_TEXT: {
|
|
316
|
+
url: '/guidelines/images',
|
|
317
|
+
issue: 'Image missing accessibility label',
|
|
318
|
+
severity: 'Critical',
|
|
319
|
+
message:
|
|
320
|
+
'This image has no accessible name. Screen reader users will not know what the image depicts.',
|
|
321
|
+
howToFix: [
|
|
322
|
+
'Add a descriptive accessibility label that explains what the image shows, for example:',
|
|
323
|
+
' • `aria-label="Product photo"`',
|
|
324
|
+
' • `aria-label="Icon for settings"`',
|
|
325
|
+
'For decorative images, mark them as non-accessible with `accessible={false}`.',
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
NO_UNDEFINED: {
|
|
330
|
+
url: '/guidelines/accessibility-label',
|
|
331
|
+
issue: 'Undefined accessibility value',
|
|
332
|
+
severity: 'Critical',
|
|
333
|
+
message:
|
|
334
|
+
'An accessibility property has an undefined value. This can cause screen readers to announce unexpected content.',
|
|
335
|
+
howToFix:
|
|
336
|
+
'Ensure all accessibility props are explicitly set to a defined value or omitted entirely.',
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
LONG_NUMBER_NOT_FORMATTED: {
|
|
340
|
+
url: '/guidelines/text',
|
|
341
|
+
issue: 'Long number not formatted for screen readers',
|
|
342
|
+
severity: 'Warning',
|
|
343
|
+
message:
|
|
344
|
+
'A long run of unformatted digits (e.g. a card or account number) is read by screen readers as one large number instead of digit-by-digit.',
|
|
345
|
+
howToFix:
|
|
346
|
+
'Add spacing between digits via the accessibility label so the number is announced digit-by-digit.',
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
: null;
|
|
350
|
+
|
|
351
|
+
const canRuleBeOverridden = __DEV__
|
|
352
|
+
? (rule: AmaRule) => {
|
|
353
|
+
return !NON_OVERRIDABLE_RULES!.includes(rule);
|
|
354
|
+
}
|
|
355
|
+
: null;
|
|
356
|
+
|
|
357
|
+
export const AMA_COLORS: { [key in A11ySeverity]: string } = {
|
|
358
|
+
Critical: '#f00',
|
|
359
|
+
Serious: '#2563EB',
|
|
360
|
+
Warning: '#2563EB',
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export default canRuleBeOverridden;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const fs_1 = __importDefault(require("fs"));
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const projectRoot = path_1.default.resolve(process.cwd(), '..', '..', '..');
|
|
9
|
-
const configFileName = 'ama.rules.json';
|
|
10
|
-
const configFilePath = path_1.default.join(projectRoot, configFileName);
|
|
11
|
-
const defaultConfig = {
|
|
12
|
-
rules: {},
|
|
13
|
-
accessibilityLabelExceptions: [],
|
|
14
|
-
};
|
|
15
|
-
if (!fs_1.default.existsSync(configFilePath)) {
|
|
16
|
-
try {
|
|
17
|
-
fs_1.default.writeFileSync(configFilePath, JSON.stringify(defaultConfig, null, 2));
|
|
18
|
-
console.log(`[AMA]: Rules file created at ${configFilePath}`);
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
console.error('[AMA]: Error creating rules file:', error);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
|
-
import type { PressableStateCallbackType } from 'react-native';
|
|
3
|
-
export declare const useButtonChecks: ((props: Record<string, any>, children?: React.ReactNode | ((state: PressableStateCallbackType) => React.ReactNode), shouldPerformContrastCheck?: boolean) => {
|
|
4
|
-
onLayout: (event: import("react-native").LayoutChangeEvent) => void;
|
|
5
|
-
debugStyle: any;
|
|
6
|
-
} | null) | null;
|