@sa11y/jest 4.0.0 → 4.1.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/automatic.d.ts +24 -0
- package/dist/automatic.d.ts.map +1 -0
- package/dist/automatic.js +87 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/matcher.d.ts +24 -0
- package/dist/matcher.d.ts.map +1 -0
- package/dist/matcher.js +71 -0
- package/dist/resultsProcessor.d.ts +10 -0
- package/dist/resultsProcessor.d.ts.map +1 -0
- package/dist/resultsProcessor.js +123 -0
- package/dist/setup.d.ts +23 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +82 -0
- package/package.json +7 -7
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for Automatic checks to be passed to {@link registerSa11yAutomaticChecks}
|
|
3
|
+
*/
|
|
4
|
+
export type AutoCheckOpts = {
|
|
5
|
+
runAfterEach?: boolean;
|
|
6
|
+
cleanupAfterEach?: boolean;
|
|
7
|
+
consolidateResults?: boolean;
|
|
8
|
+
filesFilter?: string[];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Check if current test file needs to be skipped based on any provided filter
|
|
12
|
+
*/
|
|
13
|
+
export declare function skipTest(testPath: string | undefined, filesFilter?: string[]): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Run accessibility check on each element node in the body using {@link toBeAccessible}
|
|
16
|
+
* @param opts - Options for automatic checks {@link AutoCheckOpts}
|
|
17
|
+
*/
|
|
18
|
+
export declare function automaticCheck(opts?: AutoCheckOpts): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Register accessibility checks to be run automatically after each test
|
|
21
|
+
* @param opts - Options for automatic checks {@link AutoCheckOpts}
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerSa11yAutomaticChecks(opts?: AutoCheckOpts): void;
|
|
24
|
+
//# sourceMappingURL=automatic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automatic.d.ts","sourceRoot":"","sources":["../src/automatic.ts"],"names":[],"mappings":"AAaA;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAI7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AAYF;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAUtF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,GAAE,aAAoC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B9F;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,GAAE,aAAoC,GAAG,IAAI,CAM7F"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
6
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.registerSa11yAutomaticChecks = exports.automaticCheck = exports.skipTest = void 0;
|
|
10
|
+
const common_1 = require("@sa11y/common");
|
|
11
|
+
const assert_1 = require("@sa11y/assert");
|
|
12
|
+
const format_1 = require("@sa11y/format");
|
|
13
|
+
const matcher_1 = require("./matcher");
|
|
14
|
+
const globals_1 = require("@jest/globals");
|
|
15
|
+
/**
|
|
16
|
+
* Default options when {@link registerSa11yAutomaticChecks} is invoked
|
|
17
|
+
*/
|
|
18
|
+
const defaultAutoCheckOpts = {
|
|
19
|
+
runAfterEach: true,
|
|
20
|
+
cleanupAfterEach: true,
|
|
21
|
+
consolidateResults: true,
|
|
22
|
+
filesFilter: [],
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Check if current test file needs to be skipped based on any provided filter
|
|
26
|
+
*/
|
|
27
|
+
function skipTest(testPath, filesFilter) {
|
|
28
|
+
if (!testPath || !filesFilter || !(filesFilter?.length > 0))
|
|
29
|
+
return false;
|
|
30
|
+
const skipTest = filesFilter.some((fileName) => testPath.toLowerCase().includes(fileName.toLowerCase()));
|
|
31
|
+
if (skipTest) {
|
|
32
|
+
(0, common_1.log)(`Skipping automatic accessibility check on ${testPath} as it matches given files filter: ${filesFilter.toString()}`);
|
|
33
|
+
}
|
|
34
|
+
return skipTest;
|
|
35
|
+
}
|
|
36
|
+
exports.skipTest = skipTest;
|
|
37
|
+
/**
|
|
38
|
+
* Run accessibility check on each element node in the body using {@link toBeAccessible}
|
|
39
|
+
* @param opts - Options for automatic checks {@link AutoCheckOpts}
|
|
40
|
+
*/
|
|
41
|
+
async function automaticCheck(opts = defaultAutoCheckOpts) {
|
|
42
|
+
if (skipTest(globals_1.expect.getState().testPath, opts.filesFilter))
|
|
43
|
+
return;
|
|
44
|
+
// Skip automatic check if test is using fake timer as it would result in timeout
|
|
45
|
+
if ((0, matcher_1.isTestUsingFakeTimer)()) {
|
|
46
|
+
(0, common_1.log)('Skipping automatic accessibility check as Jest fake timer is in use.');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const violations = [];
|
|
50
|
+
// Create a DOM walker filtering only elements (skipping text, comment nodes etc)
|
|
51
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
|
|
52
|
+
let currNode = walker.firstChild();
|
|
53
|
+
try {
|
|
54
|
+
while (currNode !== null) {
|
|
55
|
+
// TODO (spike): Use a logger lib with log levels selectable at runtime
|
|
56
|
+
// console.log(
|
|
57
|
+
// `♿ [DEBUG] Automatically checking a11y of ${currNode.nodeName}
|
|
58
|
+
// for test "${expect.getState().currentTestName}"
|
|
59
|
+
// : ${testPath}`
|
|
60
|
+
// );
|
|
61
|
+
violations.push(...(await (0, assert_1.getViolationsJSDOM)(currNode)));
|
|
62
|
+
currNode = walker.nextSibling();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
if (opts.cleanupAfterEach)
|
|
67
|
+
document.body.innerHTML = ''; // remove non-element nodes
|
|
68
|
+
// TODO (spike): Disable stack trace for automatic checks.
|
|
69
|
+
// Will this affect all errors globally?
|
|
70
|
+
// Error.stackTraceLimit = 0;
|
|
71
|
+
format_1.A11yError.checkAndThrow(violations, { deduplicate: opts.consolidateResults });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.automaticCheck = automaticCheck;
|
|
75
|
+
/**
|
|
76
|
+
* Register accessibility checks to be run automatically after each test
|
|
77
|
+
* @param opts - Options for automatic checks {@link AutoCheckOpts}
|
|
78
|
+
*/
|
|
79
|
+
function registerSa11yAutomaticChecks(opts = defaultAutoCheckOpts) {
|
|
80
|
+
if (opts.runAfterEach) {
|
|
81
|
+
// TODO (fix): Make registration idempotent
|
|
82
|
+
(0, common_1.log)('Registering sa11y checks to be run automatically after each test');
|
|
83
|
+
afterEach(() => automaticCheck(opts));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.registerSa11yAutomaticChecks = registerSa11yAutomaticChecks;
|
|
87
|
+
//# sourceMappingURL=automatic.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,gBAAgB,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
exports.resultsProcessor = exports.setup = exports.registerSa11yMatcher = exports.toBeAccessible = void 0;
|
|
7
|
+
/*
|
|
8
|
+
* Copyright (c) 2020, salesforce.com, inc.
|
|
9
|
+
* All rights reserved.
|
|
10
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
11
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
12
|
+
*/
|
|
13
|
+
const resultsProcessor_1 = __importDefault(require("./resultsProcessor"));
|
|
14
|
+
exports.resultsProcessor = resultsProcessor_1.default;
|
|
15
|
+
var matcher_1 = require("./matcher");
|
|
16
|
+
Object.defineProperty(exports, "toBeAccessible", { enumerable: true, get: function () { return matcher_1.toBeAccessible; } });
|
|
17
|
+
var setup_1 = require("./setup");
|
|
18
|
+
Object.defineProperty(exports, "registerSa11yMatcher", { enumerable: true, get: function () { return setup_1.registerSa11yMatcher; } });
|
|
19
|
+
Object.defineProperty(exports, "setup", { enumerable: true, get: function () { return setup_1.setup; } });
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import { A11yCheckableContext } from '@sa11y/assert';
|
|
3
|
+
import { A11yConfig } from '@sa11y/common';
|
|
4
|
+
declare global {
|
|
5
|
+
namespace jest {
|
|
6
|
+
interface Matchers<R> extends jest.CustomMatcher {
|
|
7
|
+
toBeAccessible(config?: A11yConfig): Promise<CustomMatcherResult>;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Jest expect matcher to check DOM for accessibility issues
|
|
13
|
+
* @param received - DOM or HTML Element to be tested for accessibility. Defaults to current DOM.
|
|
14
|
+
* @param config - A11yConfig to be used to test for accessibility. Defaults to `base` ruleset.
|
|
15
|
+
*/
|
|
16
|
+
export declare function toBeAccessible(received?: A11yCheckableContext, config?: A11yConfig): Promise<jest.CustomMatcherResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Detect if fake timer is being used in a jest test.
|
|
19
|
+
* Fake timers result in axe timeout https://github.com/dequelabs/axe-core/issues/3055
|
|
20
|
+
* Workaround until underlying issue can be fixed in axe.
|
|
21
|
+
* Ref: https://github.com/facebook/jest/issues/10555
|
|
22
|
+
*/
|
|
23
|
+
export declare function isTestUsingFakeTimer(): boolean;
|
|
24
|
+
//# sourceMappingURL=matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":";AASA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,eAAe,CAAC;AAGvE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,CAAC,MAAM,CAAC;IAEX,UAAU,IAAI,CAAC;QAEX,UAAU,QAAQ,CAAC,CAAC,CAAE,SAAQ,IAAI,CAAC,aAAa;YAC5C,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;SACrE;KACJ;CACJ;AAeD;;;;GAIG;AACH,wBAAsB,cAAc,CAChC,QAAQ,GAAE,oBAA+B,EACzC,MAAM,GAAE,UAA2B,GACpC,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CA8BnC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAO9C"}
|
package/dist/matcher.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2020, salesforce.com, inc.
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
6
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.isTestUsingFakeTimer = exports.toBeAccessible = void 0;
|
|
10
|
+
const jest_matcher_utils_1 = require("jest-matcher-utils");
|
|
11
|
+
const setup_1 = require("./setup");
|
|
12
|
+
const assert_1 = require("@sa11y/assert");
|
|
13
|
+
const format_1 = require("@sa11y/format");
|
|
14
|
+
const preset_rules_1 = require("@sa11y/preset-rules");
|
|
15
|
+
const fakeTimerErrMsg = 'Cannot run accessibility check when Jest fake timer is in use. ' +
|
|
16
|
+
'Switch to real timer before invoking accessibility check. ' +
|
|
17
|
+
'Ref: https://jestjs.io/docs/timer-mocks';
|
|
18
|
+
const matcherHintMsg = `expected document to have no accessibility violations but found`;
|
|
19
|
+
const expectedMsg = `0 issues`;
|
|
20
|
+
const formatOptions = {
|
|
21
|
+
a11yViolationIndicator: '⭕',
|
|
22
|
+
helpUrlIndicator: '🔗',
|
|
23
|
+
highlighter: jest_matcher_utils_1.printReceived,
|
|
24
|
+
deduplicate: false,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Jest expect matcher to check DOM for accessibility issues
|
|
28
|
+
* @param received - DOM or HTML Element to be tested for accessibility. Defaults to current DOM.
|
|
29
|
+
* @param config - A11yConfig to be used to test for accessibility. Defaults to `base` ruleset.
|
|
30
|
+
*/
|
|
31
|
+
async function toBeAccessible(received = document, config = preset_rules_1.defaultRuleset) {
|
|
32
|
+
let isAccessible = true;
|
|
33
|
+
// a11yError can be 'undefined' when used with `not` matcher and there is no error
|
|
34
|
+
let a11yError = new format_1.A11yError([], []);
|
|
35
|
+
let receivedMsg = expectedMsg;
|
|
36
|
+
if (isTestUsingFakeTimer())
|
|
37
|
+
throw new Error(fakeTimerErrMsg);
|
|
38
|
+
// TODO (Improvement): Can we detect if this is invoked async and error if not ?
|
|
39
|
+
try {
|
|
40
|
+
await (0, assert_1.assertAccessible)(received, (0, setup_1.adaptA11yConfig)(config));
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
if (e instanceof format_1.A11yError) {
|
|
44
|
+
a11yError = e;
|
|
45
|
+
isAccessible = false;
|
|
46
|
+
receivedMsg = `${a11yError.length} issues`;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
pass: isAccessible,
|
|
54
|
+
message: () => (0, jest_matcher_utils_1.matcherHint)(`${toBeAccessible.name}: ${matcherHintMsg} ${receivedMsg}: \n\n ${a11yError.format(formatOptions)}`, `${a11yError.length} issues`, expectedMsg),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
exports.toBeAccessible = toBeAccessible;
|
|
58
|
+
/**
|
|
59
|
+
* Detect if fake timer is being used in a jest test.
|
|
60
|
+
* Fake timers result in axe timeout https://github.com/dequelabs/axe-core/issues/3055
|
|
61
|
+
* Workaround until underlying issue can be fixed in axe.
|
|
62
|
+
* Ref: https://github.com/facebook/jest/issues/10555
|
|
63
|
+
*/
|
|
64
|
+
function isTestUsingFakeTimer() {
|
|
65
|
+
return (typeof jest !== 'undefined' &&
|
|
66
|
+
typeof setTimeout !== 'undefined' &&
|
|
67
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
68
|
+
(setTimeout.hasOwnProperty('_isMockFunction') || setTimeout.hasOwnProperty('clock')));
|
|
69
|
+
}
|
|
70
|
+
exports.isTestUsingFakeTimer = isTestUsingFakeTimer;
|
|
71
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AggregatedResult } from '@jest/test-result';
|
|
2
|
+
/**
|
|
3
|
+
* Custom results processor for a11y results. Only affects JSON results file output.
|
|
4
|
+
* To be used with jest cli options --json --outputFile
|
|
5
|
+
* e.g. jest --json --outputFile jestResults.json --testResultsProcessor `node_modules/@sa11y/jest/dist/resultsProcessor.js`
|
|
6
|
+
* Ref: https://jestjs.io/docs/configuration#testresultsprocessor-string
|
|
7
|
+
* - Mapping of AggregatedResult to JSON format to https://github.com/facebook/jest/blob/master/packages/jest-test-result/src/formatTestResults.ts
|
|
8
|
+
*/
|
|
9
|
+
export default function resultsProcessor(results: AggregatedResult): AggregatedResult;
|
|
10
|
+
//# sourceMappingURL=resultsProcessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resultsProcessor.d.ts","sourceRoot":"","sources":["../src/resultsProcessor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAiE,MAAM,mBAAmB,CAAC;AAuFpH;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CAyBpF"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
5
|
+
* All rights reserved.
|
|
6
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
7
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
8
|
+
*/
|
|
9
|
+
const test_result_1 = require("@jest/test-result");
|
|
10
|
+
const common_1 = require("@sa11y/common");
|
|
11
|
+
const format_1 = require("@sa11y/format");
|
|
12
|
+
// Used by downstream CI workflows to separate file/classname from attached WCAG metadata
|
|
13
|
+
const metadataSeparator = '::';
|
|
14
|
+
// Map of test suite name to test results
|
|
15
|
+
const consolidatedErrors = new Map();
|
|
16
|
+
/**
|
|
17
|
+
* Create a test failure result with a11y meta-data at forefront from given test failure
|
|
18
|
+
* with a11y error.
|
|
19
|
+
*/
|
|
20
|
+
function createA11yTestResult(testResult, a11yResult) {
|
|
21
|
+
return {
|
|
22
|
+
...testResult,
|
|
23
|
+
// TODO (refactor): extract formatting into its own function.
|
|
24
|
+
// - Can this satisfy Formatter interface?
|
|
25
|
+
// - Be part of format? (FileFormatter vs ConsoleFormatter)?
|
|
26
|
+
fullName: `${a11yResult.description}${metadataSeparator}${a11yResult.selectors}`,
|
|
27
|
+
failureMessages: [
|
|
28
|
+
`${common_1.errMsgHeader}: ${a11yResult.description}
|
|
29
|
+
Help: ${a11yResult.helpUrl}
|
|
30
|
+
CSS Selectors: ${a11yResult.selectors}
|
|
31
|
+
HTML element: ${a11yResult.html}
|
|
32
|
+
Tests: "${testResult.fullName}"
|
|
33
|
+
Summary: ${a11yResult.summary}`, // TODO (refactor): replace with array of tests when consolidating? But we reach here after de-duping a11y results?
|
|
34
|
+
],
|
|
35
|
+
// We don't need the error objects anymore as they have been processed
|
|
36
|
+
failureDetails: [],
|
|
37
|
+
// Add all test's having the same a11y issue
|
|
38
|
+
ancestorTitles: [...new Set(testResult.ancestorTitles).add(testResult.fullName)],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Convert any a11y errors from test failures into their own test suite, results
|
|
43
|
+
*/
|
|
44
|
+
function processA11yErrors(testSuite, testResult) {
|
|
45
|
+
const suiteName = testSuite.testFilePath;
|
|
46
|
+
testResult.failureDetails.forEach((failure) => {
|
|
47
|
+
let error = failure.error;
|
|
48
|
+
// If using circus test runner https://github.com/facebook/jest/issues/11405#issuecomment-843549606
|
|
49
|
+
// TODO (code cov): Add test data covering the case for circus test runner
|
|
50
|
+
/* istanbul ignore next */
|
|
51
|
+
if (error === undefined)
|
|
52
|
+
error = failure;
|
|
53
|
+
if (error.name === format_1.A11yError.name) {
|
|
54
|
+
// TODO (spike) : What happens if there are ever multiple failureDetails?
|
|
55
|
+
// Ideally there shouldn't be as test execution should be stopped on failure
|
|
56
|
+
format_1.A11yResults.add(error.a11yResults, suiteName).forEach((a11yResult) => {
|
|
57
|
+
const className = `${suiteName}${metadataSeparator}${a11yResult.wcag}`;
|
|
58
|
+
// TODO (code cov): Fix - should be covered by existing tests
|
|
59
|
+
/* istanbul ignore next */
|
|
60
|
+
if (!Array.isArray(consolidatedErrors.get(className)))
|
|
61
|
+
consolidatedErrors.set(className, []);
|
|
62
|
+
consolidatedErrors.get(className)?.push(createA11yTestResult(testResult, a11yResult));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Modify existing test result containing a11y error after a11y error
|
|
69
|
+
* is extracted into its own test result using {@link createA11yTestResult}.
|
|
70
|
+
*/
|
|
71
|
+
function modifyOriginalTestResult(results, testSuite, testResult) {
|
|
72
|
+
// Don't report the failure twice
|
|
73
|
+
testResult.status = 'disabled';
|
|
74
|
+
// TODO (fix): Remove sa11y msg from test suite message.
|
|
75
|
+
// ANSI codes and test names in suite message makes it difficult.
|
|
76
|
+
// Removing error from test result doesn't affect test suite error msg.
|
|
77
|
+
// testResult.failureMessages = [];
|
|
78
|
+
// testResult.failureDetails = [];
|
|
79
|
+
// testSuite.failureMessage = '';
|
|
80
|
+
// Suites with only a11y errors should be marked as passed
|
|
81
|
+
testSuite.numFailingTests -= 1;
|
|
82
|
+
results.numFailedTests -= 1;
|
|
83
|
+
if (testSuite.numFailingTests === 0)
|
|
84
|
+
results.numFailedTestSuites -= 1;
|
|
85
|
+
// TODO(debug): Does 'success' represent only failed tests?
|
|
86
|
+
// Or errored tests as well e.g.?
|
|
87
|
+
// if (results.numFailedTestSuites === 0) results.success = true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Custom results processor for a11y results. Only affects JSON results file output.
|
|
91
|
+
* To be used with jest cli options --json --outputFile
|
|
92
|
+
* e.g. jest --json --outputFile jestResults.json --testResultsProcessor `node_modules/@sa11y/jest/dist/resultsProcessor.js`
|
|
93
|
+
* Ref: https://jestjs.io/docs/configuration#testresultsprocessor-string
|
|
94
|
+
* - Mapping of AggregatedResult to JSON format to https://github.com/facebook/jest/blob/master/packages/jest-test-result/src/formatTestResults.ts
|
|
95
|
+
*/
|
|
96
|
+
function resultsProcessor(results) {
|
|
97
|
+
// TODO (refactor): Use map/filter to get results directly without global var for consolidated errors
|
|
98
|
+
(0, common_1.log)(`Processing ${results.numTotalTests} tests ..`);
|
|
99
|
+
results.testResults // suite results
|
|
100
|
+
.filter((testSuite) => testSuite.numFailingTests > 0)
|
|
101
|
+
.forEach((testSuite) => {
|
|
102
|
+
testSuite.testResults // individual test results
|
|
103
|
+
.filter((testResult) => testResult.status === 'failed')
|
|
104
|
+
.forEach((testResult) => {
|
|
105
|
+
processA11yErrors(testSuite, testResult);
|
|
106
|
+
modifyOriginalTestResult(results, testSuite, testResult);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
(0, common_1.log)(`Transforming a11y failures from ${consolidatedErrors.size} suites ..`);
|
|
110
|
+
consolidatedErrors.forEach((testResults, suiteKey) => {
|
|
111
|
+
const sa11ySuite = (0, test_result_1.createEmptyTestResult)();
|
|
112
|
+
// "testFilePath" gets output as suite "name" in formatted JSON result
|
|
113
|
+
sa11ySuite.testFilePath = suiteKey;
|
|
114
|
+
sa11ySuite.testResults = testResults;
|
|
115
|
+
sa11ySuite.numFailingTests = testResults.length;
|
|
116
|
+
(0, test_result_1.addResult)(results, sa11ySuite);
|
|
117
|
+
});
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
120
|
+
exports.default = resultsProcessor;
|
|
121
|
+
// The processor must be a node module that exports a function
|
|
122
|
+
module.exports = resultsProcessor;
|
|
123
|
+
//# sourceMappingURL=resultsProcessor.js.map
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { A11yConfig } from '@sa11y/common';
|
|
2
|
+
import { AutoCheckOpts } from './automatic';
|
|
3
|
+
export declare const disabledRules: string[];
|
|
4
|
+
/**
|
|
5
|
+
* Options to be passed on to {@link setup}
|
|
6
|
+
*/
|
|
7
|
+
export type Sa11yOpts = {
|
|
8
|
+
autoCheckOpts: AutoCheckOpts;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Register Sa11y Jest API and automatic checks depending on {@link Sa11yOpts}
|
|
12
|
+
* @param opts - {@link Sa11yOpts} to opt-in to automatic checks
|
|
13
|
+
*/
|
|
14
|
+
export declare function setup(opts?: Sa11yOpts): void;
|
|
15
|
+
/**
|
|
16
|
+
* Register accessibility helpers toBeAccessible as jest matchers
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerSa11yMatcher(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Customize sa11y preset rules specific to JSDOM
|
|
21
|
+
*/
|
|
22
|
+
export declare function adaptA11yConfig(config: A11yConfig, filterRules?: string[]): A11yConfig;
|
|
23
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAgC,MAAM,aAAa,CAAC;AAG1E,eAAO,MAAM,aAAa,UAiBzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB,aAAa,EAAE,aAAa,CAAC;CAKhC,CAAC;AAaF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,IAAI,GAAE,SAA4B,GAAG,IAAI,CAW9D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAU3C;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,WAAgB,GAAG,UAAU,CAK3F"}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2020, salesforce.com, inc.
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
6
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.adaptA11yConfig = exports.registerSa11yMatcher = exports.setup = exports.disabledRules = void 0;
|
|
10
|
+
const matcher_1 = require("./matcher");
|
|
11
|
+
const automatic_1 = require("./automatic");
|
|
12
|
+
const globals_1 = require("@jest/globals");
|
|
13
|
+
exports.disabledRules = [
|
|
14
|
+
// Descendancy checks that would fail at unit/component level, but pass at page level
|
|
15
|
+
'aria-required-children',
|
|
16
|
+
'aria-required-parent',
|
|
17
|
+
'dlitem',
|
|
18
|
+
'definition-list',
|
|
19
|
+
'list',
|
|
20
|
+
'listitem',
|
|
21
|
+
'landmark-one-main',
|
|
22
|
+
// color-contrast doesn't work for JSDOM and might affect performance
|
|
23
|
+
// https://github.com/dequelabs/axe-core/issues/595
|
|
24
|
+
// https://github.com/dequelabs/axe-core/blob/develop/doc/examples/jsdom/test/a11y.js
|
|
25
|
+
'color-contrast',
|
|
26
|
+
// audio, video elements are stubbed out in JSDOM
|
|
27
|
+
// https://github.com/jsdom/jsdom/issues/2155
|
|
28
|
+
'audio-caption',
|
|
29
|
+
'video-caption',
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Default options for sa11y jest matcher
|
|
33
|
+
*/
|
|
34
|
+
const defaultSa11yOpts = {
|
|
35
|
+
autoCheckOpts: {
|
|
36
|
+
runAfterEach: false,
|
|
37
|
+
cleanupAfterEach: false,
|
|
38
|
+
consolidateResults: false,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Register Sa11y Jest API and automatic checks depending on {@link Sa11yOpts}
|
|
43
|
+
* @param opts - {@link Sa11yOpts} to opt-in to automatic checks
|
|
44
|
+
*/
|
|
45
|
+
function setup(opts = defaultSa11yOpts) {
|
|
46
|
+
registerSa11yMatcher();
|
|
47
|
+
// Set defaults from env vars
|
|
48
|
+
const autoCheckOpts = opts.autoCheckOpts;
|
|
49
|
+
autoCheckOpts.runAfterEach || (autoCheckOpts.runAfterEach = !!process.env.SA11Y_AUTO);
|
|
50
|
+
// Consolidate results if automatic checks is enabled
|
|
51
|
+
autoCheckOpts.consolidateResults || (autoCheckOpts.consolidateResults = autoCheckOpts.runAfterEach);
|
|
52
|
+
autoCheckOpts.cleanupAfterEach || (autoCheckOpts.cleanupAfterEach = !!process.env.SA11Y_CLEANUP);
|
|
53
|
+
if (process.env.SA11Y_AUTO_FILTER?.trim().length)
|
|
54
|
+
autoCheckOpts.filesFilter || (autoCheckOpts.filesFilter = process.env.SA11Y_AUTO_FILTER.split(','));
|
|
55
|
+
(0, automatic_1.registerSa11yAutomaticChecks)(autoCheckOpts);
|
|
56
|
+
}
|
|
57
|
+
exports.setup = setup;
|
|
58
|
+
/**
|
|
59
|
+
* Register accessibility helpers toBeAccessible as jest matchers
|
|
60
|
+
*/
|
|
61
|
+
function registerSa11yMatcher() {
|
|
62
|
+
if (globals_1.expect !== undefined) {
|
|
63
|
+
globals_1.expect.extend({ toBeAccessible: matcher_1.toBeAccessible });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
throw new Error("Unable to find Jest's expect function." +
|
|
67
|
+
'\nPlease check your Jest installation and that you have added @sa11y/jest correctly to your jest configuration.' +
|
|
68
|
+
'\nSee https://github.com/salesforce/sa11y/tree/master/packages/jest#readme for help.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.registerSa11yMatcher = registerSa11yMatcher;
|
|
72
|
+
/**
|
|
73
|
+
* Customize sa11y preset rules specific to JSDOM
|
|
74
|
+
*/
|
|
75
|
+
function adaptA11yConfig(config, filterRules = exports.disabledRules) {
|
|
76
|
+
// TODO (refactor): Move into preset-rules pkg as a generic rules filter util
|
|
77
|
+
const adaptedConfig = JSON.parse(JSON.stringify(config));
|
|
78
|
+
adaptedConfig.runOnly.values = config.runOnly.values.filter((rule) => !filterRules.includes(rule));
|
|
79
|
+
return adaptedConfig;
|
|
80
|
+
}
|
|
81
|
+
exports.adaptA11yConfig = adaptA11yConfig;
|
|
82
|
+
//# sourceMappingURL=setup.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sa11y/jest",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Accessibility testing matcher for Jest",
|
|
5
5
|
"license": "BSD-3-Clause",
|
|
6
6
|
"homepage": "https://github.com/salesforce/sa11y/tree/master/packages/jest#readme",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@jest/test-result": "^27",
|
|
27
|
-
"@sa11y/assert": "4.
|
|
28
|
-
"@sa11y/format": "4.
|
|
29
|
-
"@sa11y/preset-rules": "4.
|
|
27
|
+
"@sa11y/assert": "4.1.0",
|
|
28
|
+
"@sa11y/format": "4.1.0",
|
|
29
|
+
"@sa11y/preset-rules": "4.1.0",
|
|
30
30
|
"jest-matcher-utils": "^27"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@jest/globals": "28.1.3",
|
|
37
|
-
"@sa11y/common": "4.
|
|
38
|
-
"@sa11y/test-utils": "4.
|
|
37
|
+
"@sa11y/common": "4.1.0",
|
|
38
|
+
"@sa11y/test-utils": "4.1.0"
|
|
39
39
|
},
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": "^14 || ^16"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "c2d36dc78a69b816b40d6c8af531786826115c13"
|
|
47
47
|
}
|