@sa11y/jest 7.1.0-alpha.1 → 7.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/README.md CHANGED
@@ -19,6 +19,7 @@ Accessibility matcher for [Jest](https://jestjs.io)
19
19
  - [JSON result transformation](#json-result-transformation)
20
20
  - [Limitations](#limitations)
21
21
  - [Disabled Checks](#disabled-checks)
22
+ - [Related Packages](#related-packages)
22
23
 
23
24
  <!-- END doctoc generated TOC please keep comment here to allow auto update -->
24
25
 
@@ -278,3 +279,7 @@ Automatic checks currently has the following limitations.
278
279
  ### Disabled Checks
279
280
 
280
281
  - @sa11y/jest automatic checks also disabled rules which were disabled in `toBeAccessible` Jest API ([disabled-checks](https://github.com/salesforce/sa11y/tree/master/packages/jest#disabled-checks))
282
+
283
+ ## Related Packages
284
+
285
+ - [`@sa11y/matcher`](../matcher/README.md): Provides the core accessibility checking APIs used by this package. Use it directly for custom test runners or advanced integrations.
@@ -1,28 +1,3 @@
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
- runDOMMutationObserver?: boolean;
10
- enableIncompleteResults?: boolean;
11
- };
12
- export declare const setOriginalDocumentBodyHtml: (bodyHtml: string | null) => void;
13
- export declare const getOriginalDocumentBodyHtml: () => string | null;
14
- /**
15
- * Check if current test file needs to be skipped based on any provided filter
16
- */
17
- export declare function skipTest(testPath: string | undefined, filesFilter?: string[]): boolean;
18
- /**
19
- * Run accessibility check on each element node in the body using {@link toBeAccessible}
20
- * @param opts - Options for automatic checks {@link AutoCheckOpts}
21
- */
22
- export declare function automaticCheck(opts?: AutoCheckOpts): Promise<void>;
23
- /**
24
- * Register accessibility checks to be run automatically after each test
25
- * @param opts - Options for automatic checks {@link AutoCheckOpts}
26
- */
1
+ import type { AutoCheckOpts } from '@sa11y/matcher';
27
2
  export declare function registerSa11yAutomaticChecks(opts?: AutoCheckOpts): void;
28
3
  //# sourceMappingURL=automatic.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"automatic.d.ts","sourceRoot":"","sources":["../src/automatic.ts"],"names":[],"mappings":"AAmBA;;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;IACvB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACrC,CAAC;AAkBF,eAAO,MAAM,2BAA2B,aAAc,MAAM,GAAG,IAAI,SAElE,CAAC;AAEF,eAAO,MAAM,2BAA2B,qBAEvC,CAAC;AAEF;;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,CAqE9F;AAsBD;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,GAAE,aAAoC,GAAG,IAAI,CAmB7F"}
1
+ {"version":3,"file":"automatic.d.ts","sourceRoot":"","sources":["../src/automatic.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,wBAAgB,4BAA4B,CAAC,IAAI,GAAE,aAAoC,GAAG,IAAI,CAc7F"}
package/dist/automatic.js CHANGED
@@ -1,165 +1,29 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSa11yAutomaticChecks = void 0;
2
4
  /*
3
- * Copyright (c) 2021, salesforce.com, inc.
5
+ * Copyright (c) 2025, salesforce.com, inc.
4
6
  * All rights reserved.
5
7
  * SPDX-License-Identifier: BSD-3-Clause
6
8
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7
9
  */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.registerSa11yAutomaticChecks = exports.automaticCheck = exports.skipTest = exports.getOriginalDocumentBodyHtml = exports.setOriginalDocumentBodyHtml = 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
10
  const globals_1 = require("@jest/globals");
15
- const preset_rules_1 = require("@sa11y/preset-rules");
16
- /**
17
- * Default options when {@link registerSa11yAutomaticChecks} is invoked
18
- */
19
- const defaultAutoCheckOpts = {
20
- runAfterEach: true,
21
- cleanupAfterEach: true,
22
- consolidateResults: true,
23
- filesFilter: [],
24
- runDOMMutationObserver: false,
25
- enableIncompleteResults: false,
26
- };
27
- let originalDocumentBodyHtml = null;
28
- let mutatedNodes = [];
29
- const setOriginalDocumentBodyHtml = (bodyHtml) => {
30
- originalDocumentBodyHtml = bodyHtml ?? null;
31
- };
32
- exports.setOriginalDocumentBodyHtml = setOriginalDocumentBodyHtml;
33
- const getOriginalDocumentBodyHtml = () => {
34
- return originalDocumentBodyHtml;
35
- };
36
- exports.getOriginalDocumentBodyHtml = getOriginalDocumentBodyHtml;
37
- /**
38
- * Check if current test file needs to be skipped based on any provided filter
39
- */
40
- function skipTest(testPath, filesFilter) {
41
- if (!testPath || !filesFilter || !(filesFilter?.length > 0))
42
- return false;
43
- const skipTest = filesFilter.some((fileName) => testPath.toLowerCase().includes(fileName.toLowerCase()));
44
- if (skipTest) {
45
- (0, common_1.log)(`Skipping automatic accessibility check on ${testPath} as it matches given files filter: ${filesFilter.toString()}`);
46
- }
47
- return skipTest;
48
- }
49
- exports.skipTest = skipTest;
50
- /**
51
- * Run accessibility check on each element node in the body using {@link toBeAccessible}
52
- * @param opts - Options for automatic checks {@link AutoCheckOpts}
53
- */
54
- async function automaticCheck(opts = defaultAutoCheckOpts) {
55
- if (skipTest(globals_1.expect.getState().testPath, opts.filesFilter))
56
- return;
57
- // Skip automatic check if test is using fake timer as it would result in timeout
58
- if ((0, matcher_1.isTestUsingFakeTimer)()) {
59
- (0, common_1.log)('Skipping automatic accessibility check as Jest fake timer is in use.');
11
+ const matcher_1 = require("@sa11y/matcher");
12
+ const matcher_2 = require("./matcher");
13
+ function registerSa11yAutomaticChecks(opts = matcher_1.defaultAutoCheckOpts) {
14
+ if (!opts.runAfterEach)
60
15
  return;
61
- }
62
- const customRules = (0, common_1.useCustomRules)();
63
- let config = customRules.length === 0
64
- ? (0, preset_rules_1.adaptA11yConfig)(preset_rules_1.defaultRuleset)
65
- : (0, preset_rules_1.adaptA11yConfigCustomRules)(preset_rules_1.defaultRuleset, customRules);
66
- if (opts.enableIncompleteResults)
67
- config = (0, preset_rules_1.adaptA11yConfigIncompleteResults)(config);
68
- let a11yResults = [];
69
- const currentDocumentHtml = document.body.innerHTML;
70
- if (originalDocumentBodyHtml) {
71
- document.body.innerHTML = originalDocumentBodyHtml;
72
- }
73
- // Create a DOM walker filtering only elements (skipping text, comment nodes etc)
74
- const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
75
- let currNode = walker.firstChild();
76
- try {
77
- if (!opts.runDOMMutationObserver) {
78
- while (currNode !== null) {
79
- // TODO (spike): Use a logger lib with log levels selectable at runtime
80
- // console.log(
81
- // `♿ [DEBUG] Automatically checking a11y of ${currNode.nodeName}
82
- // for test "${expect.getState().currentTestName}"
83
- // : ${testPath}`
84
- // );
85
- // W-10004832 - Exclude descendancy based rules from automatic checks
86
- a11yResults.push(...(await (0, assert_1.getA11yResultsJSDOM)(currNode, config, opts.enableIncompleteResults)));
87
- currNode = walker.nextSibling();
88
- }
89
- }
90
- else {
91
- a11yResults.push(...(await (0, assert_1.getA11yResultsJSDOM)(document.body, config, opts.enableIncompleteResults)));
92
- document.body.innerHTML = '';
93
- // loop mutated nodes
94
- for await (const mutatedNode of mutatedNodes) {
95
- if (mutatedNode) {
96
- document.body.innerHTML = mutatedNode;
97
- a11yResults.push(...(await (0, assert_1.getA11yResultsJSDOM)(document.body, config, opts.enableIncompleteResults)));
98
- }
99
- }
100
- }
101
- }
102
- catch (e) {
103
- format_1.AxeError.throwAxeError(e);
104
- }
105
- finally {
16
+ const observer = new MutationObserver(matcher_1.mutationObserverCallback);
17
+ beforeEach(() => {
106
18
  if (opts.runDOMMutationObserver) {
107
- mutatedNodes = [];
108
- }
109
- (0, exports.setOriginalDocumentBodyHtml)(null);
110
- document.body.innerHTML = currentDocumentHtml;
111
- if (opts.cleanupAfterEach)
112
- document.body.innerHTML = ''; // remove non-element nodes
113
- // TODO (spike): Disable stack trace for automatic checks.
114
- // Will this affect all errors globally?
115
- // Error.stackTraceLimit = 0;
116
- if (process.env.SELECTOR_FILTER_KEYWORDS) {
117
- a11yResults = (0, format_1.exceptionListFilterSelectorKeywords)(a11yResults, process.env.SELECTOR_FILTER_KEYWORDS.split(','));
19
+ observer.observe(document.body, matcher_1.observerOptions);
118
20
  }
119
- format_1.A11yError.checkAndThrow(a11yResults, { deduplicate: opts.consolidateResults });
120
- }
121
- }
122
- exports.automaticCheck = automaticCheck;
123
- function observerCallback(mutations, _observer) {
124
- for (const mutation of mutations) {
125
- mutation.addedNodes.forEach((node) => {
126
- if (node?.parentElement?.innerHTML) {
127
- mutatedNodes.push(node.parentElement.innerHTML);
128
- }
129
- else if (node?.outerHTML) {
130
- mutatedNodes.push(node?.outerHTML);
131
- }
132
- });
133
- }
134
- }
135
- // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit
136
- const observerOptions = {
137
- subtree: true,
138
- childList: true,
139
- attributes: true,
140
- characterData: true, // monitor changes to the character data contained within nodes
141
- };
142
- /**
143
- * Register accessibility checks to be run automatically after each test
144
- * @param opts - Options for automatic checks {@link AutoCheckOpts}
145
- */
146
- function registerSa11yAutomaticChecks(opts = defaultAutoCheckOpts) {
147
- if (opts.runAfterEach) {
148
- const observer = new MutationObserver(observerCallback);
149
- // TODO (fix): Make registration idempotent
150
- (0, common_1.log)('Registering sa11y checks to be run automatically after each test');
151
- beforeEach(() => {
152
- if (opts.runDOMMutationObserver) {
153
- observer.observe(document.body, observerOptions);
154
- }
155
- });
156
- afterEach(async () => {
157
- if (opts.runDOMMutationObserver) {
158
- observer.disconnect(); // stop mutation observer
159
- }
160
- await automaticCheck(opts);
161
- });
162
- }
21
+ });
22
+ afterEach(async () => {
23
+ if (opts.runDOMMutationObserver)
24
+ observer.disconnect();
25
+ await (0, matcher_1.runAutomaticCheck)(opts, globals_1.expect.getState().testPath, matcher_2.isTestUsingFakeTimer);
26
+ });
163
27
  }
164
28
  exports.registerSa11yAutomaticChecks = registerSa11yAutomaticChecks;
165
29
  //# sourceMappingURL=automatic.js.map
@@ -1,20 +1,4 @@
1
1
  import { AggregatedResult } from '@jest/test-result';
2
- export type ErrorElement = {
3
- html: string;
4
- selectors: string;
5
- hierarchy: string;
6
- any: string;
7
- all: string;
8
- none: string;
9
- relatedNodeAny: string;
10
- relatedNodeAll: string;
11
- relatedNodeNone: string;
12
- message?: string;
13
- };
14
- /**
15
- * Create a test failure html elements array grouped by rule violation
16
- */
17
- export declare function createA11yErrorElements(errorElements: ErrorElement[]): string;
18
2
  /**
19
3
  * Custom results processor for a11y results grouping the violations. Only affects JSON results file output.
20
4
  * To be used with jest cli options --json --outputFile
@@ -1 +1 @@
1
- {"version":3,"file":"groupViolationResultsProcessor.d.ts","sourceRoot":"","sources":["../src/groupViolationResultsProcessor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAA+B,MAAM,mBAAmB,CAAC;AAoBlF,MAAM,MAAM,YAAY,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAoBF;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,YAAY,EAAE,UAqDpE;AAsHD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CAa5E;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CAaxF"}
1
+ {"version":3,"file":"groupViolationResultsProcessor.d.ts","sourceRoot":"","sources":["../src/groupViolationResultsProcessor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAA+B,MAAM,mBAAmB,CAAC;AA0DlF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CAa5E;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CAaxF"}
@@ -1,142 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resultsProcessorManualChecks = exports.resultsProcessor = exports.createA11yErrorElements = void 0;
3
+ exports.resultsProcessorManualChecks = exports.resultsProcessor = void 0;
4
4
  const common_1 = require("@sa11y/common");
5
5
  const format_1 = require("@sa11y/format");
6
- const formatSpacing = '\t'.repeat(4);
7
- const formatForAxeMessage = `\n${formatSpacing}\t`;
8
- const axeMessages = {
9
- toSolveAny: `${formatForAxeMessage}- More Info: To solve the problem, you need to fix at least (1) of the following:\n`,
10
- toSolveFirst: `${formatForAxeMessage}- More Info: To solve the problem, you need to fix one of the following:\n`,
11
- toSolveSecond: `${formatForAxeMessage}- And fix the following:\n`,
12
- relatedNodes: `${formatForAxeMessage}- Related Nodes: \n`,
13
- };
14
- /**
15
- * Create a test failure html elements array grouped by rule violation
16
- */
17
- function createA11yErrorElements(errorElements) {
18
- const a11yErrorElements = [];
19
- let onlyOneSummary = false;
20
- const appendRelatedNodes = (errorMessage, relatedNodes) => {
21
- return relatedNodes ? `${errorMessage}${axeMessages.relatedNodes}${relatedNodes}` : errorMessage;
22
- };
23
- errorElements.forEach((errorElement, index) => {
24
- let errorMessage = `${formatSpacing}(${index + 1}) - HTML element : ${errorElement.html
25
- .replace(/&lt;/g, '<')
26
- .replace(/&gt;/, '>')}`;
27
- errorMessage += `${formatForAxeMessage}- CSS selector(s) : ${errorElement.selectors.replace(/&gt;/, '>')}`;
28
- errorMessage += `${formatForAxeMessage}- HTML Tag Hierarchy : ${errorElement.hierarchy}`;
29
- if (errorElement.message)
30
- errorMessage += `${formatForAxeMessage}- Error Message (Needs Review) : ${errorElement.message}`;
31
- const isAny = errorElement.any?.length > 0;
32
- const isAll = errorElement.all?.length > 0;
33
- const isNone = errorElement.none?.length > 0;
34
- if (!onlyOneSummary) {
35
- if (isAny) {
36
- errorMessage += `${axeMessages.toSolveAny}${errorElement.any}`;
37
- errorMessage = appendRelatedNodes(errorMessage, errorElement.relatedNodeAny);
38
- if (isAll) {
39
- errorMessage += `${axeMessages.toSolveSecond}${errorElement.all}`;
40
- errorMessage = appendRelatedNodes(errorMessage, errorElement.relatedNodeAll);
41
- }
42
- if (isNone) {
43
- errorMessage += `${axeMessages.toSolveSecond}${errorElement.none}`;
44
- errorMessage = appendRelatedNodes(errorMessage, errorElement.relatedNodeNone);
45
- }
46
- }
47
- else if (isAll) {
48
- errorMessage += `${axeMessages.toSolveFirst}${errorElement.all}`;
49
- errorMessage = appendRelatedNodes(errorMessage, errorElement.relatedNodeAll);
50
- if (isNone) {
51
- errorMessage += `${axeMessages.toSolveSecond}${errorElement.none}`;
52
- errorMessage = appendRelatedNodes(errorMessage, errorElement.relatedNodeNone);
53
- }
54
- }
55
- else {
56
- if (isNone) {
57
- errorMessage += `${axeMessages.toSolveFirst}${errorElement.none}`;
58
- errorMessage = appendRelatedNodes(errorMessage, errorElement.relatedNodeNone);
59
- }
60
- }
61
- }
62
- else {
63
- errorMessage = appendRelatedNodes(errorMessage, errorElement.relatedNodeAny);
64
- }
65
- onlyOneSummary = isAny && !isAll && !isNone;
66
- a11yErrorElements.push(errorMessage);
67
- });
68
- return a11yErrorElements.join('\n');
69
- }
70
- exports.createA11yErrorElements = createA11yErrorElements;
71
- /**
72
- * Create a test failure violation error message grouped by rule violation
73
- */
74
- function createA11yRuleViolation(a11yRule, ruleIndex) {
75
- return `(${ruleIndex}) [${a11yRule.id}] ${a11yRule.description}
76
- * Error element(s) : ${a11yRule.errorElements.length}\n${createA11yErrorElements(a11yRule.errorElements)}
77
- * Help:
78
- • Help URL: ${a11yRule.helpUrl}
79
- • WCAG Criteria: ${a11yRule.wcagCriteria}`;
80
- }
81
- /**
82
- * Create a test processA11yDetailsAndMessages violation error message grouped by rule violation
83
- */
84
- function processA11yDetailsAndMessages(error, a11yFailureMessages) {
85
- const a11yRuleViolations = {};
86
- let a11yRuleViolationsCount = 0;
87
- let a11yErrorElementsCount = 0;
88
- error.a11yResults.forEach((a11yResult) => {
89
- a11yErrorElementsCount++;
90
- if (!a11yRuleViolations[a11yResult.wcag]) {
91
- a11yRuleViolationsCount++;
92
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
93
- a11yRuleViolations[a11yResult.wcag] = {
94
- id: a11yResult.id,
95
- description: a11yResult.description,
96
- helpUrl: a11yResult.helpUrl,
97
- wcagCriteria: a11yResult.wcag,
98
- summary: a11yResult.summary,
99
- errorElements: [],
100
- };
101
- }
102
- a11yRuleViolations[a11yResult.wcag].errorElements.push({
103
- html: a11yResult.html,
104
- selectors: a11yResult.selectors,
105
- hierarchy: a11yResult.ancestry,
106
- any: a11yResult.any,
107
- all: a11yResult.all,
108
- none: a11yResult.none,
109
- relatedNodeAny: a11yResult.relatedNodeAny,
110
- relatedNodeAll: a11yResult.relatedNodeAll,
111
- relatedNodeNone: a11yResult.relatedNodeNone,
112
- message: a11yResult?.message,
113
- });
114
- });
115
- const a11yFailureMessage = `
116
- The test has failed the accessibility check. Accessibility Stacktrace/Issues:
117
- ${a11yErrorElementsCount} HTML elements have accessibility issue(s). ${a11yRuleViolationsCount} rules failed.
118
-
119
- ${Object.values(a11yRuleViolations)
120
- .map((a11yRuleViolation, index) => createA11yRuleViolation(a11yRuleViolation, index + 1))
121
- .join('\n')}
122
-
123
-
124
- For more info about automated accessibility testing: https://sfdc.co/a11y-test
125
- For tips on fixing accessibility bugs: https://sfdc.co/a11y
126
- For technical questions regarding Salesforce accessibility tools, contact our Sa11y team: http://sfdc.co/sa11y-users
127
- For guidance on accessibility related specifics, contact our A11y team: http://sfdc.co/tmp-a11y
128
- `;
129
- a11yFailureMessages.push(a11yFailureMessage);
130
- }
6
+ const matcher_1 = require("@sa11y/matcher");
131
7
  /**
132
8
  * Convert any a11y errors from test failures into their own test suite, results
133
9
  */
134
10
  function processA11yErrors(results, testSuite, testResult) {
135
11
  const a11yFailureDetails = [];
136
- const axeFailureDetails = [];
137
12
  const a11yFailureMessages = [];
138
- const axeFailureMessages = [];
139
- let a11yAxeErrorsExist = false;
13
+ let a11yErrorsExist = false;
140
14
  testResult.failureDetails.forEach((failure) => {
141
15
  let error = failure.error;
142
16
  // If using circus test runner https://github.com/facebook/jest/issues/11405#issuecomment-843549606
@@ -145,35 +19,27 @@ function processA11yErrors(results, testSuite, testResult) {
145
19
  if (error === undefined)
146
20
  error = failure;
147
21
  if (error.name === format_1.A11yError.name) {
148
- a11yAxeErrorsExist = true;
22
+ a11yErrorsExist = true;
149
23
  a11yFailureDetails.push({ ...failure });
150
- processA11yDetailsAndMessages(error, a11yFailureMessages);
151
- }
152
- if (error.name === format_1.AxeError.name) {
153
- a11yAxeErrorsExist = true;
154
- axeFailureDetails.push({ ...failure });
155
- axeFailureMessages.push(`
156
- The test has failed to execute the accessibility check.
157
- Please contact our Sa11y team: http://sfdc.co/sa11y-users
158
- `);
24
+ (0, matcher_1.processA11yDetailsAndMessages)(error, a11yFailureMessages);
159
25
  }
160
26
  });
161
- if (!a11yAxeErrorsExist) {
27
+ if (!a11yErrorsExist) {
162
28
  testSuite.numFailingTests -= 1;
163
29
  results.numFailedTests -= 1;
164
30
  if (testSuite.numFailingTests === 0)
165
31
  results.numFailedTestSuites -= 1;
166
32
  }
167
- testResult.failureDetails = [...a11yFailureDetails, ...axeFailureDetails];
168
- testResult.failureMessages = [...a11yFailureMessages, ...axeFailureMessages];
169
- testResult.status = testResult.failureDetails.length === 0 ? 'passed' : testResult.status;
33
+ testResult.failureDetails = [...a11yFailureDetails];
34
+ testResult.failureMessages = [...a11yFailureMessages];
35
+ testResult.status = a11yFailureDetails.length === 0 ? 'passed' : testResult.status;
170
36
  }
171
37
  function processA11yMatcherErrors(results, testSuite, testResult) {
172
38
  const a11yFailureMessages = [];
173
39
  testResult.failureDetails.forEach((failure) => {
174
40
  const error = failure?.error?.matcherResult?.a11yError;
175
41
  if (error !== undefined) {
176
- processA11yDetailsAndMessages(error, a11yFailureMessages);
42
+ (0, matcher_1.processA11yDetailsAndMessages)(error, a11yFailureMessages);
177
43
  testResult.failureMessages = [...a11yFailureMessages];
178
44
  }
179
45
  });
@@ -218,7 +84,6 @@ exports.resultsProcessorManualChecks = resultsProcessorManualChecks;
218
84
  const exportedFunctions = {
219
85
  resultsProcessor,
220
86
  resultsProcessorManualChecks,
221
- createA11yErrorElements,
222
87
  };
223
88
  module.exports = exportedFunctions;
224
89
  //# sourceMappingURL=groupViolationResultsProcessor.js.map
package/dist/matcher.d.ts CHANGED
@@ -1,22 +1,6 @@
1
1
  /// <reference types="jest" />
2
2
  import { A11yCheckableContext } from '@sa11y/assert';
3
- import { A11yError } from '@sa11y/format';
4
3
  import { A11yConfig } from '@sa11y/common';
5
- declare global {
6
- namespace jest {
7
- interface Matchers<R> extends jest.CustomMatcher {
8
- toBeAccessible(config?: A11yConfig): Promise<CustomMatcherResult>;
9
- }
10
- }
11
- }
12
- /**
13
- * Jest expect matcher to check DOM for accessibility issues
14
- * @param received - DOM or HTML Element to be tested for accessibility. Defaults to current DOM.
15
- * @param config - A11yConfig to be used to test for accessibility. Defaults to `base` ruleset.
16
- */
17
- export declare function toBeAccessible(received?: A11yCheckableContext, config?: A11yConfig): Promise<jest.CustomMatcherResult & {
18
- a11yError: A11yError;
19
- }>;
20
4
  /**
21
5
  * Detect if fake timer is being used in a jest test.
22
6
  * Fake timers result in axe timeout https://github.com/dequelabs/axe-core/issues/3055
@@ -24,4 +8,7 @@ export declare function toBeAccessible(received?: A11yCheckableContext, config?:
24
8
  * Ref: https://github.com/facebook/jest/issues/10555
25
9
  */
26
10
  export declare function isTestUsingFakeTimer(): boolean;
11
+ export declare function toBeAccessible(received?: A11yCheckableContext, config?: A11yConfig): Promise<jest.CustomMatcherResult & {
12
+ a11yError: Error;
13
+ }>;
27
14
  //# sourceMappingURL=matcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,SAAS,EAAW,MAAM,eAAe,CAAC;AAEnD,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,GAAG;IAAE,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC,CA+B9D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAO9C"}
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":";AAOA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAO9C;AAED,wBAAsB,cAAc,CAChC,QAAQ,GAAE,oBAA+B,EACzC,MAAM,GAAE,UAA2B,GACpC,OAAO,CAAC,IAAI,CAAC,mBAAmB,GAAG;IAAE,SAAS,EAAE,KAAK,CAAA;CAAE,CAAC,CAc1D"}
package/dist/matcher.js CHANGED
@@ -1,60 +1,15 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toBeAccessible = exports.isTestUsingFakeTimer = void 0;
2
4
  /*
3
- * Copyright (c) 2020, salesforce.com, inc.
5
+ * Copyright (c) 2025, salesforce.com, inc.
4
6
  * All rights reserved.
5
7
  * SPDX-License-Identifier: BSD-3-Clause
6
8
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7
9
  */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.isTestUsingFakeTimer = exports.toBeAccessible = void 0;
10
10
  const jest_matcher_utils_1 = require("jest-matcher-utils");
11
- const assert_1 = require("@sa11y/assert");
12
- const format_1 = require("@sa11y/format");
13
11
  const preset_rules_1 = require("@sa11y/preset-rules");
14
- const fakeTimerErrMsg = 'Cannot run accessibility check when Jest fake timer is in use. ' +
15
- 'Switch to real timer before invoking accessibility check. ' +
16
- 'Ref: https://jestjs.io/docs/timer-mocks';
17
- const matcherHintMsg = `expected document to have no accessibility violations but found`;
18
- const expectedMsg = `0 issues`;
19
- const formatOptions = {
20
- a11yViolationIndicator: '⭕',
21
- helpUrlIndicator: '🔗',
22
- highlighter: jest_matcher_utils_1.printReceived,
23
- deduplicate: false,
24
- };
25
- /**
26
- * Jest expect matcher to check DOM for accessibility issues
27
- * @param received - DOM or HTML Element to be tested for accessibility. Defaults to current DOM.
28
- * @param config - A11yConfig to be used to test for accessibility. Defaults to `base` ruleset.
29
- */
30
- async function toBeAccessible(received = document, config = preset_rules_1.defaultRuleset) {
31
- let isAccessible = true;
32
- // a11yError can be 'undefined' when used with `not` matcher and there is no error
33
- let a11yError = new format_1.A11yError([], []);
34
- let receivedMsg = expectedMsg;
35
- if (isTestUsingFakeTimer())
36
- throw new Error(fakeTimerErrMsg);
37
- // TODO (Improvement): Can we detect if this is invoked async and error if not ?
38
- try {
39
- await (0, assert_1.assertAccessible)(received, (0, preset_rules_1.adaptA11yConfig)(config));
40
- }
41
- catch (e) {
42
- if (e instanceof format_1.A11yError) {
43
- a11yError = e;
44
- isAccessible = false;
45
- receivedMsg = `${a11yError.length} issues`;
46
- }
47
- else {
48
- throw e;
49
- }
50
- }
51
- return {
52
- pass: isAccessible,
53
- message: () => (0, jest_matcher_utils_1.matcherHint)(`${toBeAccessible.name}: ${matcherHintMsg} ${receivedMsg}: \n\n ${a11yError.format(formatOptions)}`, `${a11yError.length} issues`, expectedMsg),
54
- a11yError: a11yError,
55
- };
56
- }
57
- exports.toBeAccessible = toBeAccessible;
12
+ const matcher_1 = require("@sa11y/matcher");
58
13
  /**
59
14
  * Detect if fake timer is being used in a jest test.
60
15
  * Fake timers result in axe timeout https://github.com/dequelabs/axe-core/issues/3055
@@ -68,4 +23,19 @@ function isTestUsingFakeTimer() {
68
23
  (setTimeout.hasOwnProperty('_isMockFunction') || setTimeout.hasOwnProperty('clock')));
69
24
  }
70
25
  exports.isTestUsingFakeTimer = isTestUsingFakeTimer;
26
+ async function toBeAccessible(received = document, config = preset_rules_1.defaultRuleset) {
27
+ if (isTestUsingFakeTimer())
28
+ throw new Error(matcher_1.fakeTimerErrMsg);
29
+ const { isAccessible, a11yError, receivedMsg } = await (0, matcher_1.runA11yCheck)(received, config);
30
+ return {
31
+ pass: isAccessible,
32
+ message: () => (0, jest_matcher_utils_1.matcherHint)(`toBeAccessible`) +
33
+ `\n\nExpected: no accessibility violations\nReceived: ${receivedMsg}\n\n${a11yError.format({
34
+ ...matcher_1.formatOptions,
35
+ highlighter: jest_matcher_utils_1.printReceived,
36
+ })}`,
37
+ a11yError,
38
+ };
39
+ }
40
+ exports.toBeAccessible = toBeAccessible;
71
41
  //# sourceMappingURL=matcher.js.map
package/dist/setup.d.ts CHANGED
@@ -1,17 +1,3 @@
1
- import { AutoCheckOpts } from './automatic';
2
- /**
3
- * Options to be passed on to {@link setup}
4
- */
5
- export type Sa11yOpts = {
6
- autoCheckOpts: AutoCheckOpts;
7
- };
8
- /**
9
- * Register Sa11y Jest API and automatic checks depending on {@link Sa11yOpts}
10
- * @param opts - {@link Sa11yOpts} to opt-in to automatic checks
11
- */
12
- export declare function setup(opts?: Sa11yOpts): void;
13
- /**
14
- * Register accessibility helpers toBeAccessible as jest matchers
15
- */
1
+ export declare function setup(opts?: import("@sa11y/matcher/dist/setup").Sa11yOpts): void;
16
2
  export declare function registerSa11yMatcher(): void;
17
3
  //# sourceMappingURL=setup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AASA,OAAO,EACH,aAAa,EAIhB,MAAM,aAAa,CAAC;AAIrB;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB,aAAa,EAAE,aAAa,CAAC;CAKhC,CAAC;AAoCF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,IAAI,GAAE,SAA4B,GAAG,IAAI,CAiD9D;AACD;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAU3C"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAiBA,wBAAgB,KAAK,CAAC,IAAI,gDAAmB,GAAG,IAAI,CAanD;AACD,wBAAgB,oBAAoB,IAAI,IAAI,CAU3C"}
package/dist/setup.js CHANGED
@@ -1,104 +1,32 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSa11yMatcher = exports.setup = void 0;
2
4
  /*
3
- * Copyright (c) 2020, salesforce.com, inc.
5
+ * Copyright (c) 2025, salesforce.com, inc.
4
6
  * All rights reserved.
5
7
  * SPDX-License-Identifier: BSD-3-Clause
6
8
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7
9
  */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.registerSa11yMatcher = exports.setup = void 0;
10
- const matcher_1 = require("./matcher");
11
- const common_1 = require("@sa11y/common");
12
- const automatic_1 = require("./automatic");
13
10
  const globals_1 = require("@jest/globals");
14
- const preset_rules_1 = require("@sa11y/preset-rules");
15
- /**
16
- * Default options for sa11y jest matcher
17
- */
18
- const defaultSa11yOpts = {
19
- autoCheckOpts: {
20
- runAfterEach: false,
21
- cleanupAfterEach: false,
22
- consolidateResults: false,
23
- runDOMMutationObserver: false,
24
- enableIncompleteResults: false,
25
- },
26
- };
27
- function registerRemoveChild() {
28
- // eslint-disable-next-line @typescript-eslint/unbound-method
29
- const originalRemoveChild = Element.prototype.removeChild;
30
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
31
- Element.prototype.removeChild = function (oldChild) {
32
- if (oldChild.parentNode === this) {
33
- // Your custom implementation here
34
- if (!(0, automatic_1.getOriginalDocumentBodyHtml)()) {
35
- (0, automatic_1.setOriginalDocumentBodyHtml)(document?.body?.innerHTML ?? '');
36
- }
37
- }
38
- return originalRemoveChild.call(this, oldChild);
39
- };
40
- }
41
- const improvedChecksFilter = [
42
- 'ui-email-stream-components/modules/emailStream/adminHealthInsights/__tests__/adminHealthInsights.test.js',
43
- ];
44
- /**
45
- * Register Sa11y Jest API and automatic checks depending on {@link Sa11yOpts}
46
- * @param opts - {@link Sa11yOpts} to opt-in to automatic checks
47
- */
48
- function setup(opts = defaultSa11yOpts) {
49
- if (process.env.SA11Y_AUTO) {
50
- const testPath = globals_1.expect.getState().testPath ?? '';
51
- const ignoreImprovedChecks = improvedChecksFilter.some((fileName) => testPath.toLowerCase().includes(fileName.toLowerCase()));
52
- if (!ignoreImprovedChecks) {
53
- registerRemoveChild();
54
- }
11
+ const matcher_1 = require("@sa11y/matcher");
12
+ const automatic_1 = require("./automatic");
13
+ const matcher_2 = require("./matcher");
14
+ function setup(opts = matcher_1.defaultSa11yOpts) {
15
+ const testPath = globals_1.expect.getState().testPath ?? '';
16
+ const ignoreImprovedChecks = matcher_1.improvedChecksFilter.some((fileName) => testPath.toLowerCase().includes(fileName.toLowerCase()));
17
+ if (process.env.SA11Y_AUTO && !ignoreImprovedChecks) {
18
+ (0, matcher_1.registerRemoveChild)();
55
19
  }
56
20
  registerSa11yMatcher();
57
- (0, common_1.registerCustomRules)(preset_rules_1.changesData, preset_rules_1.rulesData, preset_rules_1.checkData);
58
- // Set defaults from env vars
21
+ (0, matcher_1.registerCustomSa11yRules)();
59
22
  const autoCheckOpts = opts.autoCheckOpts;
60
- autoCheckOpts.runAfterEach || (autoCheckOpts.runAfterEach = !!process.env.SA11Y_AUTO);
61
- // Consolidate results if automatic checks is enabled
62
- autoCheckOpts.consolidateResults || (autoCheckOpts.consolidateResults = autoCheckOpts.runAfterEach);
63
- autoCheckOpts.cleanupAfterEach || (autoCheckOpts.cleanupAfterEach = !!process.env.SA11Y_CLEANUP);
64
- if (process.env.SA11Y_AUTO_FILTER?.trim().length)
65
- autoCheckOpts.filesFilter || (autoCheckOpts.filesFilter = process.env.SA11Y_AUTO_FILTER.split(','));
66
- const exemptedFiles = (0, common_1.useFilesToBeExempted)();
67
- if (exemptedFiles.length !== 0) {
68
- autoCheckOpts.filesFilter = (autoCheckOpts.filesFilter ?? []).concat(exemptedFiles);
69
- }
70
- // TODO remove @W-14447754 - add files filter
71
- autoCheckOpts.filesFilter = (autoCheckOpts.filesFilter ?? []).concat([
72
- 'ui-help-components/modules/forceHelp/linkToReleaseNotes/__tests__/linkToReleaseNotes.spec.js',
73
- 'ui-help-components/modules/forceHelp/linkToNonSalesforceResource/__tests__/linkToNonSalesforceResource.spec.js',
74
- 'ui-help-components/modules/forceHelp/linkToAppexchange/__tests__/linkToAppexchange.spec.js',
75
- 'ui-help-components/modules/forceHelp/linkToTrailblazer/__tests__/linkToTrailblazer.spec.js',
76
- 'ui-help-components/modules/forceHelp/linkToVidyard/__tests__/linkToVidyard.spec.js',
77
- 'ui-help-components/modules/forceHelp/linkToSalesforceDevelopers/__tests__/linkToSalesforceDevelopers.spec.js',
78
- 'ui-help-components/modules/forceHelp/linkToWebinar/__tests__/linkToWebinar.spec.js',
79
- 'ui-help-components/modules/forceHelp/linkToTrust/__tests__/linkToTrust.spec.js',
80
- 'ui-help-components/modules/forceHelp/linkToPartnerCommunity/__tests__/linkToPartnerCommunity.spec.js',
81
- 'ui-help-components/modules/forceHelp/linkToDocResource/__tests__/linkToDocResource.spec.js',
82
- 'ui-help-components/modules/forceHelp/searchResultItem/__tests__/searchResultItem.spec.js',
83
- 'ui-help-components/modules/forceHelp/linkToTrailhead/__tests__/linkToTrailhead.spec.js',
84
- 'ui-help-components/modules/forceHelp/linkToSalesforceSuccess/__tests__/linkToSalesforceSuccess.spec.js',
85
- 'ui-help-components/modules/forceHelp/linkToSalesforceHelp/__tests__/linkToSalesforceHelp.spec.js',
86
- 'ui-help-components/modules/forceHelp/link/__tests__/link.spec.js',
87
- 'ui-help-components/modules/forceHelp/searchResults/__tests__/searchResults.spec.js',
88
- 'ui-help-components/modules/forceHelp/linkToKnownIssue/__tests__/linkToKnownIssue.spec.js',
89
- ]);
90
- // set the flag to true to run sa11y in DOM Mutation Observer mode
91
- autoCheckOpts.runDOMMutationObserver || (autoCheckOpts.runDOMMutationObserver = !!process.env.SA11Y_ENABLE_DOM_MUTATION_OBSERVER);
92
- autoCheckOpts.enableIncompleteResults || (autoCheckOpts.enableIncompleteResults = !!process.env.SA11Y_ENABLE_INCOMPLETE_RESULTS);
23
+ (0, matcher_1.updateAutoCheckOpts)(autoCheckOpts);
93
24
  (0, automatic_1.registerSa11yAutomaticChecks)(autoCheckOpts);
94
25
  }
95
26
  exports.setup = setup;
96
- /**
97
- * Register accessibility helpers toBeAccessible as jest matchers
98
- */
99
27
  function registerSa11yMatcher() {
100
28
  if (globals_1.expect !== undefined) {
101
- globals_1.expect.extend({ toBeAccessible: matcher_1.toBeAccessible });
29
+ globals_1.expect.extend({ toBeAccessible: matcher_2.toBeAccessible });
102
30
  }
103
31
  else {
104
32
  throw new Error("Unable to find Jest's expect function." +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sa11y/jest",
3
- "version": "7.1.0-alpha.1",
3
+ "version": "7.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,10 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "@jest/test-result": "^27",
27
- "@sa11y/assert": "7.1.0-alpha.1",
28
- "@sa11y/format": "7.1.0-alpha.1",
29
- "@sa11y/preset-rules": "7.1.0-alpha.1",
27
+ "@sa11y/assert": "7.1.0",
28
+ "@sa11y/format": "7.1.0",
29
+ "@sa11y/matcher": "7.1.0",
30
+ "@sa11y/preset-rules": "7.1.0",
30
31
  "jest-matcher-utils": "^27"
31
32
  },
32
33
  "peerDependencies": {
@@ -34,8 +35,8 @@
34
35
  },
35
36
  "devDependencies": {
36
37
  "@jest/globals": "28.1.3",
37
- "@sa11y/common": "7.1.0-alpha.1",
38
- "@sa11y/test-utils": "7.1.0-alpha.1"
38
+ "@sa11y/common": "7.1.0",
39
+ "@sa11y/test-utils": "7.1.0"
39
40
  },
40
41
  "publishConfig": {
41
42
  "access": "public"
@@ -43,5 +44,5 @@
43
44
  "engines": {
44
45
  "node": "^18 || ^20"
45
46
  },
46
- "gitHead": "d8b64c285f066b8dcc955882c5bf399ef6b1c279"
47
+ "gitHead": "eb40172b0b4925c307bb99a83858dca52415abf8"
47
48
  }