@sa11y/jest 7.1.0-alpha.2 → 7.1.1

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
 
@@ -108,7 +109,7 @@ it('should be accessible', async () => {
108
109
  // Setup DOM to be tested for accessibility
109
110
  //...
110
111
 
111
- // assert that whole DOM is accessible (using base preset-rule)
112
+ // assert that DOM is accessible (using base preset-rule)
112
113
  await expect(document).toBeAccessible();
113
114
 
114
115
  // Can be used to test accessibility of a specific HTML element
@@ -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,35 +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 type RenderedDOMSaveOpts = {
13
- renderedDOMDumpDirPath?: string;
14
- generateRenderedDOMFileSaveLocation?: (testFilePath: string, testName: string) => {
15
- fileName: string;
16
- fileUrl: string;
17
- };
18
- };
19
- export declare const setOriginalDocumentBodyHtml: (bodyHtml: string | null) => void;
20
- export declare const getOriginalDocumentBodyHtml: () => string | null;
21
- /**
22
- * Check if current test file needs to be skipped based on any provided filter
23
- */
24
- export declare function skipTest(testPath: string | undefined, filesFilter?: string[]): boolean;
25
- /**
26
- * Run accessibility check on each element node in the body using {@link toBeAccessible}
27
- * @param opts - Options for automatic checks {@link AutoCheckOpts}
28
- */
29
- export declare function automaticCheck(opts?: AutoCheckOpts, renderedDOMSaveOpts?: RenderedDOMSaveOpts): Promise<void>;
30
- /**
31
- * Register accessibility checks to be run automatically after each test
32
- * @param opts - Options for automatic checks {@link AutoCheckOpts}
33
- */
34
- export declare function registerSa11yAutomaticChecks(opts?: AutoCheckOpts, renderedDOMSaveOpts?: RenderedDOMSaveOpts): void;
1
+ import type { AutoCheckOpts } from '@sa11y/matcher';
2
+ export declare function registerSa11yAutomaticChecks(opts?: AutoCheckOpts): void;
35
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;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAC9B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,mCAAmC,CAAC,EAAE,CAClC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,KACf;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C,CAAC;AAsBF,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,CAChC,IAAI,GAAE,aAAoC,EAC1C,mBAAmB,GAAE,mBAAgD,GACtE,OAAO,CAAC,IAAI,CAAC,CAmGf;AAsBD;;;GAGG;AACH,wBAAgB,4BAA4B,CACxC,IAAI,GAAE,aAAoC,EAC1C,mBAAmB,GAAE,mBAAgD,GACtE,IAAI,CAmBN"}
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,188 +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
- const defaultRenderedDOMSaveOpts = {
28
- renderedDOMDumpDirPath: '',
29
- };
30
- let originalDocumentBodyHtml = null;
31
- let mutatedNodes = [];
32
- const setOriginalDocumentBodyHtml = (bodyHtml) => {
33
- originalDocumentBodyHtml = bodyHtml ?? null;
34
- };
35
- exports.setOriginalDocumentBodyHtml = setOriginalDocumentBodyHtml;
36
- const getOriginalDocumentBodyHtml = () => {
37
- return originalDocumentBodyHtml;
38
- };
39
- exports.getOriginalDocumentBodyHtml = getOriginalDocumentBodyHtml;
40
- /**
41
- * Check if current test file needs to be skipped based on any provided filter
42
- */
43
- function skipTest(testPath, filesFilter) {
44
- if (!testPath || !filesFilter || !(filesFilter?.length > 0))
45
- return false;
46
- const skipTest = filesFilter.some((fileName) => testPath.toLowerCase().includes(fileName.toLowerCase()));
47
- if (skipTest) {
48
- (0, common_1.log)(`Skipping automatic accessibility check on ${testPath} as it matches given files filter: ${filesFilter.toString()}`);
49
- }
50
- return skipTest;
51
- }
52
- exports.skipTest = skipTest;
53
- /**
54
- * Run accessibility check on each element node in the body using {@link toBeAccessible}
55
- * @param opts - Options for automatic checks {@link AutoCheckOpts}
56
- */
57
- async function automaticCheck(opts = defaultAutoCheckOpts, renderedDOMSaveOpts = defaultRenderedDOMSaveOpts) {
58
- if (skipTest(globals_1.expect.getState().testPath, opts.filesFilter))
59
- return;
60
- // Skip automatic check if test is using fake timer as it would result in timeout
61
- if ((0, matcher_1.isTestUsingFakeTimer)()) {
62
- (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)
63
15
  return;
64
- }
65
- const customRules = (0, common_1.useCustomRules)();
66
- let config = customRules.length === 0
67
- ? (0, preset_rules_1.adaptA11yConfig)(preset_rules_1.defaultRuleset)
68
- : (0, preset_rules_1.adaptA11yConfigCustomRules)(preset_rules_1.defaultRuleset, customRules);
69
- if (opts.enableIncompleteResults)
70
- config = (0, preset_rules_1.adaptA11yConfigIncompleteResults)(config);
71
- let a11yResults = [];
72
- const currentDocumentHtml = document.body.innerHTML;
73
- if (originalDocumentBodyHtml) {
74
- document.body.innerHTML = originalDocumentBodyHtml;
75
- }
76
- // Create a DOM walker filtering only elements (skipping text, comment nodes etc)
77
- const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
78
- let currNode = walker.firstChild();
79
- let renderedDOMSavedFileName = '';
80
- try {
81
- if (!opts.runDOMMutationObserver) {
82
- while (currNode !== null) {
83
- // TODO (spike): Use a logger lib with log levels selectable at runtime
84
- // console.log(
85
- // `♿ [DEBUG] Automatically checking a11y of ${currNode.nodeName}
86
- // for test "${expect.getState().currentTestName}"
87
- // : ${testPath}`
88
- // );
89
- // W-10004832 - Exclude descendancy based rules from automatic checks
90
- a11yResults.push(...(await (0, assert_1.getA11yResultsJSDOM)(currNode, config, opts.enableIncompleteResults)));
91
- currNode = walker.nextSibling();
92
- }
93
- }
94
- else {
95
- const a11yResultsJSDOM = await (0, assert_1.getA11yResultsJSDOM)(document.body, config, opts.enableIncompleteResults);
96
- if (a11yResultsJSDOM?.length > 0) {
97
- if (!!renderedDOMSaveOpts.renderedDOMDumpDirPath &&
98
- !!renderedDOMSaveOpts.generateRenderedDOMFileSaveLocation) {
99
- try {
100
- // save the document body HTML
101
- const testFilePath = globals_1.expect.getState().testPath ?? '';
102
- const testName = globals_1.expect.getState().currentTestName ?? '';
103
- if (!testFilePath || !testName) {
104
- console.log(`Skipping saving rendered DOM HTML as one or both of test file path and test name are empty`);
105
- }
106
- else {
107
- const { fileName, fileUrl } = renderedDOMSaveOpts.generateRenderedDOMFileSaveLocation(testFilePath, testName);
108
- renderedDOMSavedFileName = fileUrl;
109
- (0, common_1.writeHtmlFileInPath)(renderedDOMSaveOpts.renderedDOMDumpDirPath, fileName, document.body.innerHTML);
110
- }
111
- }
112
- catch (e) {
113
- console.log(`ran into an error while saving rendered DOM - ${e.message}`);
114
- }
115
- }
116
- }
117
- a11yResults.push(...a11yResultsJSDOM);
118
- document.body.innerHTML = '';
119
- // loop mutated nodes
120
- for await (const mutatedNode of mutatedNodes) {
121
- if (mutatedNode) {
122
- document.body.innerHTML = mutatedNode;
123
- a11yResults.push(...(await (0, assert_1.getA11yResultsJSDOM)(document.body, config, opts.enableIncompleteResults)));
124
- }
125
- }
126
- }
127
- }
128
- finally {
16
+ const observer = new MutationObserver(matcher_1.mutationObserverCallback);
17
+ beforeEach(() => {
129
18
  if (opts.runDOMMutationObserver) {
130
- mutatedNodes = [];
131
- }
132
- (0, exports.setOriginalDocumentBodyHtml)(null);
133
- document.body.innerHTML = currentDocumentHtml;
134
- if (opts.cleanupAfterEach)
135
- document.body.innerHTML = ''; // remove non-element nodes
136
- // TODO (spike): Disable stack trace for automatic checks.
137
- // Will this affect all errors globally?
138
- // Error.stackTraceLimit = 0;
139
- if (process.env.SELECTOR_FILTER_KEYWORDS) {
140
- a11yResults = (0, format_1.exceptionListFilterSelectorKeywords)(a11yResults, process.env.SELECTOR_FILTER_KEYWORDS.split(','));
19
+ observer.observe(document.body, matcher_1.observerOptions);
141
20
  }
142
- format_1.A11yError.checkAndThrow(a11yResults, { deduplicate: opts.consolidateResults, renderedDOMSavedFileName });
143
- }
144
- }
145
- exports.automaticCheck = automaticCheck;
146
- function observerCallback(mutations, _observer) {
147
- for (const mutation of mutations) {
148
- mutation.addedNodes.forEach((node) => {
149
- if (node?.parentElement?.innerHTML) {
150
- mutatedNodes.push(node.parentElement.innerHTML);
151
- }
152
- else if (node?.outerHTML) {
153
- mutatedNodes.push(node?.outerHTML);
154
- }
155
- });
156
- }
157
- }
158
- // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit
159
- const observerOptions = {
160
- subtree: true,
161
- childList: true,
162
- attributes: true,
163
- characterData: true, // monitor changes to the character data contained within nodes
164
- };
165
- /**
166
- * Register accessibility checks to be run automatically after each test
167
- * @param opts - Options for automatic checks {@link AutoCheckOpts}
168
- */
169
- function registerSa11yAutomaticChecks(opts = defaultAutoCheckOpts, renderedDOMSaveOpts = defaultRenderedDOMSaveOpts) {
170
- if (opts.runAfterEach) {
171
- const observer = new MutationObserver(observerCallback);
172
- // TODO (fix): Make registration idempotent
173
- (0, common_1.log)('Registering sa11y checks to be run automatically after each test');
174
- beforeEach(() => {
175
- if (opts.runDOMMutationObserver) {
176
- observer.observe(document.body, observerOptions);
177
- }
178
- });
179
- afterEach(async () => {
180
- if (opts.runDOMMutationObserver) {
181
- observer.disconnect(); // stop mutation observer
182
- }
183
- await automaticCheck(opts, renderedDOMSaveOpts);
184
- });
185
- }
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
+ });
186
27
  }
187
28
  exports.registerSa11yAutomaticChecks = registerSa11yAutomaticChecks;
188
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;AAelF,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;AA4GD;;;;;;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,134 +1,9 @@
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
- ${error.renderedDOMSavedFileName ? `HTML Source: ${error.renderedDOMSavedFileName}\n` : ''}
117
- The test has failed the accessibility check. Accessibility Stacktrace/Issues:
118
- ${a11yErrorElementsCount} HTML elements have accessibility issue(s). ${a11yRuleViolationsCount} rules failed.
119
-
120
- ${Object.values(a11yRuleViolations)
121
- .map((a11yRuleViolation, index) => createA11yRuleViolation(a11yRuleViolation, index + 1))
122
- .join('\n')}
123
-
124
-
125
- For more info about automated accessibility testing: https://sfdc.co/a11y-test
126
- For tips on fixing accessibility bugs: https://sfdc.co/a11y
127
- For technical questions regarding Salesforce accessibility tools, contact our Sa11y team: http://sfdc.co/sa11y-users
128
- For guidance on accessibility related specifics, contact our A11y team: http://sfdc.co/tmp-a11y
129
- `;
130
- a11yFailureMessages.push(a11yFailureMessage);
131
- }
6
+ const matcher_1 = require("@sa11y/matcher");
132
7
  /**
133
8
  * Convert any a11y errors from test failures into their own test suite, results
134
9
  */
@@ -146,7 +21,7 @@ function processA11yErrors(results, testSuite, testResult) {
146
21
  if (error.name === format_1.A11yError.name) {
147
22
  a11yErrorsExist = true;
148
23
  a11yFailureDetails.push({ ...failure });
149
- processA11yDetailsAndMessages(error, a11yFailureMessages);
24
+ (0, matcher_1.processA11yDetailsAndMessages)(error, a11yFailureMessages);
150
25
  }
151
26
  });
152
27
  if (!a11yErrorsExist) {
@@ -164,7 +39,7 @@ function processA11yMatcherErrors(results, testSuite, testResult) {
164
39
  testResult.failureDetails.forEach((failure) => {
165
40
  const error = failure?.error?.matcherResult?.a11yError;
166
41
  if (error !== undefined) {
167
- processA11yDetailsAndMessages(error, a11yFailureMessages);
42
+ (0, matcher_1.processA11yDetailsAndMessages)(error, a11yFailureMessages);
168
43
  testResult.failureMessages = [...a11yFailureMessages];
169
44
  }
170
45
  });
@@ -209,7 +84,6 @@ exports.resultsProcessorManualChecks = resultsProcessorManualChecks;
209
84
  const exportedFunctions = {
210
85
  resultsProcessor,
211
86
  resultsProcessorManualChecks,
212
- createA11yErrorElements,
213
87
  };
214
88
  module.exports = exportedFunctions;
215
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,18 +1,3 @@
1
- import { AutoCheckOpts, RenderedDOMSaveOpts } from './automatic';
2
- /**
3
- * Options to be passed on to {@link setup}
4
- */
5
- export type Sa11yOpts = {
6
- autoCheckOpts: AutoCheckOpts;
7
- renderedDOMSaveOpts: RenderedDOMSaveOpts;
8
- };
9
- /**
10
- * Register Sa11y Jest API and automatic checks depending on {@link Sa11yOpts}
11
- * @param opts - {@link Sa11yOpts} to opt-in to automatic checks
12
- */
13
- export declare function setup(opts?: Sa11yOpts): void;
14
- /**
15
- * Register accessibility helpers toBeAccessible as jest matchers
16
- */
1
+ export declare function setup(opts?: import("@sa11y/matcher/dist/setup").Sa11yOpts): void;
17
2
  export declare function registerSa11yMatcher(): void;
18
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,EAIb,mBAAmB,EACtB,MAAM,aAAa,CAAC;AAIrB;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB,aAAa,EAAE,aAAa,CAAC;IAC7B,mBAAmB,EAAE,mBAAmB,CAAC;CAK5C,CAAC;AAuCF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,IAAI,GAAE,SAA4B,GAAG,IAAI,CAmD9D;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,108 +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
- renderedDOMSaveOpts: {
27
- renderedDOMDumpDirPath: '',
28
- },
29
- };
30
- function registerRemoveChild() {
31
- // eslint-disable-next-line @typescript-eslint/unbound-method
32
- const originalRemoveChild = Element.prototype.removeChild;
33
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
34
- Element.prototype.removeChild = function (oldChild) {
35
- if (oldChild.parentNode === this) {
36
- // Your custom implementation here
37
- if (!(0, automatic_1.getOriginalDocumentBodyHtml)()) {
38
- (0, automatic_1.setOriginalDocumentBodyHtml)(document?.body?.innerHTML ?? '');
39
- }
40
- }
41
- return originalRemoveChild.call(this, oldChild);
42
- };
43
- }
44
- const improvedChecksFilter = [
45
- 'ui-email-stream-components/modules/emailStream/adminHealthInsights/__tests__/adminHealthInsights.test.js',
46
- ];
47
- /**
48
- * Register Sa11y Jest API and automatic checks depending on {@link Sa11yOpts}
49
- * @param opts - {@link Sa11yOpts} to opt-in to automatic checks
50
- */
51
- function setup(opts = defaultSa11yOpts) {
52
- if (process.env.SA11Y_AUTO) {
53
- const testPath = globals_1.expect.getState().testPath ?? '';
54
- const ignoreImprovedChecks = improvedChecksFilter.some((fileName) => testPath.toLowerCase().includes(fileName.toLowerCase()));
55
- if (!ignoreImprovedChecks) {
56
- registerRemoveChild();
57
- }
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)();
58
19
  }
59
20
  registerSa11yMatcher();
60
- (0, common_1.registerCustomRules)(preset_rules_1.changesData, preset_rules_1.rulesData, preset_rules_1.checkData);
61
- // Set defaults from env vars
21
+ (0, matcher_1.registerCustomSa11yRules)();
62
22
  const autoCheckOpts = opts.autoCheckOpts;
63
- autoCheckOpts.runAfterEach || (autoCheckOpts.runAfterEach = !!process.env.SA11Y_AUTO);
64
- // Consolidate results if automatic checks is enabled
65
- autoCheckOpts.consolidateResults || (autoCheckOpts.consolidateResults = autoCheckOpts.runAfterEach);
66
- autoCheckOpts.cleanupAfterEach || (autoCheckOpts.cleanupAfterEach = !!process.env.SA11Y_CLEANUP);
67
- if (process.env.SA11Y_AUTO_FILTER?.trim().length)
68
- autoCheckOpts.filesFilter || (autoCheckOpts.filesFilter = process.env.SA11Y_AUTO_FILTER.split(','));
69
- const exemptedFiles = (0, common_1.useFilesToBeExempted)();
70
- if (exemptedFiles.length !== 0) {
71
- autoCheckOpts.filesFilter = (autoCheckOpts.filesFilter ?? []).concat(exemptedFiles);
72
- }
73
- // TODO remove @W-14447754 - add files filter
74
- autoCheckOpts.filesFilter = (autoCheckOpts.filesFilter ?? []).concat([
75
- 'ui-help-components/modules/forceHelp/linkToReleaseNotes/__tests__/linkToReleaseNotes.spec.js',
76
- 'ui-help-components/modules/forceHelp/linkToNonSalesforceResource/__tests__/linkToNonSalesforceResource.spec.js',
77
- 'ui-help-components/modules/forceHelp/linkToAppexchange/__tests__/linkToAppexchange.spec.js',
78
- 'ui-help-components/modules/forceHelp/linkToTrailblazer/__tests__/linkToTrailblazer.spec.js',
79
- 'ui-help-components/modules/forceHelp/linkToVidyard/__tests__/linkToVidyard.spec.js',
80
- 'ui-help-components/modules/forceHelp/linkToSalesforceDevelopers/__tests__/linkToSalesforceDevelopers.spec.js',
81
- 'ui-help-components/modules/forceHelp/linkToWebinar/__tests__/linkToWebinar.spec.js',
82
- 'ui-help-components/modules/forceHelp/linkToTrust/__tests__/linkToTrust.spec.js',
83
- 'ui-help-components/modules/forceHelp/linkToPartnerCommunity/__tests__/linkToPartnerCommunity.spec.js',
84
- 'ui-help-components/modules/forceHelp/linkToDocResource/__tests__/linkToDocResource.spec.js',
85
- 'ui-help-components/modules/forceHelp/searchResultItem/__tests__/searchResultItem.spec.js',
86
- 'ui-help-components/modules/forceHelp/linkToTrailhead/__tests__/linkToTrailhead.spec.js',
87
- 'ui-help-components/modules/forceHelp/linkToSalesforceSuccess/__tests__/linkToSalesforceSuccess.spec.js',
88
- 'ui-help-components/modules/forceHelp/linkToSalesforceHelp/__tests__/linkToSalesforceHelp.spec.js',
89
- 'ui-help-components/modules/forceHelp/link/__tests__/link.spec.js',
90
- 'ui-help-components/modules/forceHelp/searchResults/__tests__/searchResults.spec.js',
91
- 'ui-help-components/modules/forceHelp/linkToKnownIssue/__tests__/linkToKnownIssue.spec.js',
92
- ]);
93
- // set the flag to true to run sa11y in DOM Mutation Observer mode
94
- autoCheckOpts.runDOMMutationObserver || (autoCheckOpts.runDOMMutationObserver = !!process.env.SA11Y_ENABLE_DOM_MUTATION_OBSERVER);
95
- autoCheckOpts.enableIncompleteResults || (autoCheckOpts.enableIncompleteResults = !!process.env.SA11Y_ENABLE_INCOMPLETE_RESULTS);
96
- const renderedDOMSaveOpts = opts.renderedDOMSaveOpts;
97
- (0, automatic_1.registerSa11yAutomaticChecks)(autoCheckOpts, renderedDOMSaveOpts);
23
+ (0, matcher_1.updateAutoCheckOpts)(autoCheckOpts);
24
+ (0, automatic_1.registerSa11yAutomaticChecks)(autoCheckOpts);
98
25
  }
99
26
  exports.setup = setup;
100
- /**
101
- * Register accessibility helpers toBeAccessible as jest matchers
102
- */
103
27
  function registerSa11yMatcher() {
104
28
  if (globals_1.expect !== undefined) {
105
- globals_1.expect.extend({ toBeAccessible: matcher_1.toBeAccessible });
29
+ globals_1.expect.extend({ toBeAccessible: matcher_2.toBeAccessible });
106
30
  }
107
31
  else {
108
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.2",
3
+ "version": "7.1.1",
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.2",
28
- "@sa11y/format": "7.1.0-alpha.2",
29
- "@sa11y/preset-rules": "7.1.0-alpha.2",
27
+ "@sa11y/assert": "7.1.1",
28
+ "@sa11y/format": "7.1.1",
29
+ "@sa11y/matcher": "7.1.1",
30
+ "@sa11y/preset-rules": "7.1.1",
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.2",
38
- "@sa11y/test-utils": "7.1.0-alpha.2"
38
+ "@sa11y/common": "7.1.1",
39
+ "@sa11y/test-utils": "7.1.1"
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": "108d459f5c12828a7071769bef8812be898df56b"
47
+ "gitHead": "30752b4258f1eca16f1c6e984d6bb088820b6e33"
47
48
  }